chore: merge feature/1.2-crypto-argon2id-keychain into main
This commit is contained in:
commit
f596ff46cb
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Rust build artifacts
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
@ -2,6 +2,60 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chronogrid_ai"
|
name = "chronogrid_ai"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -13,7 +67,459 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chronogrid_crypto"
|
name = "chronogrid_crypto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
|
"keyring",
|
||||||
|
"rand",
|
||||||
|
"thiserror",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chronogrid_net"
|
name = "chronogrid_net"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "dbus"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus-secret-service"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||||
|
dependencies = [
|
||||||
|
"dbus",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyring"
|
||||||
|
version = "3.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"dbus-secret-service",
|
||||||
|
"log",
|
||||||
|
"security-framework 2.11.1",
|
||||||
|
"security-framework 3.7.0",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[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 = "pkg-config"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[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-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[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.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "windows-targets"
|
||||||
|
version = "0.53.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,15 @@
|
||||||
- **Status**: Accepted
|
- **Status**: Accepted
|
||||||
- **Context**: Design a multi-platform (Windows, Linux, Android, Headless) productivity engine for time, project, and note management without centralized servers.
|
- **Context**: Design a multi-platform (Windows, Linux, Android, Headless) productivity engine for time, project, and note management without centralized servers.
|
||||||
- **Decision**: Adopted Rust core with SQLCipher local storage, Automerge CRDT delta sync over libp2p, and Flutter presentation layer bound via C-FFI.
|
- **Decision**: Adopted Rust core with SQLCipher local storage, Automerge CRDT delta sync over libp2p, and Flutter presentation layer bound via C-FFI.
|
||||||
|
|
||||||
|
## [2026-08-09] - ADL-002: chronogrid_crypto Crate – Argon2id KDF & OS Keychain
|
||||||
|
|
||||||
|
- **Status**: Accepted
|
||||||
|
- **Context**: Task 1.2 required implementing the `chronogrid_crypto` crate with Argon2id master-key derivation and OS-native keychain bindings per `docs/ARCHITECTURE/02-security-and-crypto.md`.
|
||||||
|
- **Decision**:
|
||||||
|
- Argon2id parameters pinned: 64 MiB memory, 3 iterations, 4 threads, 16-byte random salt, 32-byte output.
|
||||||
|
- `DerivedKey` struct implements `Zeroize + Drop` to scrub sensitive memory on scope exit.
|
||||||
|
- OS keychain abstracted via `keyring` crate v3 (Windows Credential Manager / DPAPI, Linux SecretService / libsecret). Android keychain is excluded (`cfg(not(target_os = "android"))`) and will be handled by Flutter KeyStore bridge in Task 1.5.
|
||||||
|
- Raw key material is Base64-encoded before credential-store insertion for UTF-8 backend compatibility.
|
||||||
|
- Inline Base64 encode/decode helpers avoid adding an external Base64 dependency.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,13 @@ rust-version.workspace = true
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
argon2 = "0.5"
|
||||||
|
rand = "0.8"
|
||||||
|
thiserror = "2"
|
||||||
|
zeroize = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
# OS keychain support (not available on Android – handled via Flutter/NDK bridge)
|
||||||
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["sync-secret-service", "windows-native", "apple-native"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! Error types for the `chronogrid_crypto` crate.
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Unified error type for all cryptographic operations.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CryptoError {
|
||||||
|
/// Argon2id hashing failed internally.
|
||||||
|
#[error("Argon2id key derivation failed: {0}")]
|
||||||
|
KeyDerivation(String),
|
||||||
|
|
||||||
|
/// The provided salt has an invalid length (expected 16 bytes).
|
||||||
|
#[error("invalid salt length: expected 16 bytes, got {0}")]
|
||||||
|
InvalidSaltLength(usize),
|
||||||
|
|
||||||
|
/// OS keychain operation failed.
|
||||||
|
#[error("keychain error: {0}")]
|
||||||
|
Keychain(String),
|
||||||
|
|
||||||
|
/// The requested credential was not found in the OS keychain.
|
||||||
|
#[error("credential not found in keychain for service='{service}', account='{account}'")]
|
||||||
|
CredentialNotFound {
|
||||||
|
service: String,
|
||||||
|
account: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A generic / unexpected internal error.
|
||||||
|
#[error("internal crypto error: {0}")]
|
||||||
|
Internal(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience alias used throughout the crate.
|
||||||
|
pub type Result<T> = std::result::Result<T, CryptoError>;
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
//! Argon2id master-key derivation.
|
||||||
|
//!
|
||||||
|
//! Parameters are pinned to the values mandated by
|
||||||
|
//! `docs/ARCHITECTURE/02-security-and-crypto.md`:
|
||||||
|
//!
|
||||||
|
//! | Parameter | Value |
|
||||||
|
//! |-------------|---------------|
|
||||||
|
//! | Memory | 64 MiB (65 536 KiB) |
|
||||||
|
//! | Time cost | 3 iterations |
|
||||||
|
//! | Parallelism | 4 threads |
|
||||||
|
//! | Salt | 16 random bytes |
|
||||||
|
//! | Output | 32 bytes (256-bit master key) |
|
||||||
|
|
||||||
|
use argon2::{Algorithm, Argon2, Params, Version};
|
||||||
|
use rand::RngCore;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::error::{CryptoError, Result};
|
||||||
|
|
||||||
|
// ── Spec-mandated constants ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Memory cost in KiB (64 MiB).
|
||||||
|
const ARGON2_MEMORY_KIB: u32 = 65_536;
|
||||||
|
|
||||||
|
/// Time cost (iterations).
|
||||||
|
const ARGON2_TIME_COST: u32 = 3;
|
||||||
|
|
||||||
|
/// Degree of parallelism.
|
||||||
|
const ARGON2_PARALLELISM: u32 = 4;
|
||||||
|
|
||||||
|
/// Length of the derived key in bytes.
|
||||||
|
pub const DERIVED_KEY_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// Required salt length in bytes.
|
||||||
|
pub const SALT_LEN: usize = 16;
|
||||||
|
|
||||||
|
// ── Public types ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Container for a derived master key and its associated salt.
|
||||||
|
///
|
||||||
|
/// Implements [`Zeroize`] + [`Drop`] so sensitive bytes are wiped from memory
|
||||||
|
/// when the value goes out of scope.
|
||||||
|
#[derive(Zeroize)]
|
||||||
|
#[zeroize(drop)]
|
||||||
|
pub struct DerivedKey {
|
||||||
|
/// 32-byte derived master key.
|
||||||
|
key: [u8; DERIVED_KEY_LEN],
|
||||||
|
/// 16-byte salt that was used during derivation.
|
||||||
|
salt: [u8; SALT_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerivedKey {
|
||||||
|
/// Returns the derived 32-byte key material.
|
||||||
|
#[inline]
|
||||||
|
pub fn key(&self) -> &[u8; DERIVED_KEY_LEN] {
|
||||||
|
&self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the 16-byte salt used during derivation.
|
||||||
|
#[inline]
|
||||||
|
pub fn salt(&self) -> &[u8; SALT_LEN] {
|
||||||
|
&self.salt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes self and returns the raw key bytes.
|
||||||
|
///
|
||||||
|
/// **Warning**: The caller is responsible for zeroing these bytes after use.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_key_bytes(self) -> [u8; DERIVED_KEY_LEN] {
|
||||||
|
let k = self.key;
|
||||||
|
// Prevent the Drop impl from double-zeroing by forgetting self.
|
||||||
|
std::mem::forget(self);
|
||||||
|
k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for DerivedKey {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("DerivedKey")
|
||||||
|
.field("key", &"[REDACTED]")
|
||||||
|
.field("salt_len", &self.salt.len())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Core derivation function ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Generates a cryptographically random 16-byte salt.
|
||||||
|
pub fn generate_salt() -> [u8; SALT_LEN] {
|
||||||
|
let mut salt = [0u8; SALT_LEN];
|
||||||
|
rand::thread_rng().fill_bytes(&mut salt);
|
||||||
|
salt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives a 256-bit master key from `passphrase` using Argon2id.
|
||||||
|
///
|
||||||
|
/// If `salt` is `None`, a fresh 16-byte random salt is generated.
|
||||||
|
/// If `salt` is `Some(bytes)`, the provided slice **must** be exactly 16 bytes.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`CryptoError::InvalidSaltLength`] if a provided salt is not 16 bytes.
|
||||||
|
/// Returns [`CryptoError::KeyDerivation`] if the Argon2 hasher fails internally.
|
||||||
|
pub fn derive_master_key(passphrase: &str, salt: Option<&[u8]>) -> Result<DerivedKey> {
|
||||||
|
// Resolve or generate salt.
|
||||||
|
let salt_bytes: [u8; SALT_LEN] = match salt {
|
||||||
|
Some(s) => {
|
||||||
|
if s.len() != SALT_LEN {
|
||||||
|
return Err(CryptoError::InvalidSaltLength(s.len()));
|
||||||
|
}
|
||||||
|
let mut arr = [0u8; SALT_LEN];
|
||||||
|
arr.copy_from_slice(s);
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
None => generate_salt(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build Argon2id with spec parameters.
|
||||||
|
let params = Params::new(
|
||||||
|
ARGON2_MEMORY_KIB,
|
||||||
|
ARGON2_TIME_COST,
|
||||||
|
ARGON2_PARALLELISM,
|
||||||
|
Some(DERIVED_KEY_LEN),
|
||||||
|
)
|
||||||
|
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
|
||||||
|
|
||||||
|
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
||||||
|
|
||||||
|
// Derive key into output buffer.
|
||||||
|
let mut key_buf = [0u8; DERIVED_KEY_LEN];
|
||||||
|
argon2
|
||||||
|
.hash_password_into(passphrase.as_bytes(), &salt_bytes, &mut key_buf)
|
||||||
|
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(DerivedKey {
|
||||||
|
key: key_buf,
|
||||||
|
salt: salt_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derive_key_with_random_salt() {
|
||||||
|
let dk = derive_master_key("correct horse battery staple", None)
|
||||||
|
.expect("derivation should succeed");
|
||||||
|
assert_eq!(dk.key().len(), DERIVED_KEY_LEN);
|
||||||
|
assert_eq!(dk.salt().len(), SALT_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derive_key_deterministic_with_same_salt() {
|
||||||
|
let salt = [0xABu8; SALT_LEN];
|
||||||
|
let dk1 = derive_master_key("hunter2", Some(&salt)).unwrap();
|
||||||
|
let dk2 = derive_master_key("hunter2", Some(&salt)).unwrap();
|
||||||
|
assert_eq!(dk1.key(), dk2.key(), "same passphrase + salt must yield identical key");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn different_passphrases_yield_different_keys() {
|
||||||
|
let salt = [0x01u8; SALT_LEN];
|
||||||
|
let dk1 = derive_master_key("alpha", Some(&salt)).unwrap();
|
||||||
|
let dk2 = derive_master_key("beta", Some(&salt)).unwrap();
|
||||||
|
assert_ne!(dk1.key(), dk2.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reject_invalid_salt_length() {
|
||||||
|
let bad_salt = [0u8; 10];
|
||||||
|
let res = derive_master_key("pw", Some(&bad_salt));
|
||||||
|
assert!(res.is_err());
|
||||||
|
match res.unwrap_err() {
|
||||||
|
CryptoError::InvalidSaltLength(len) => assert_eq!(len, 10),
|
||||||
|
other => panic!("expected InvalidSaltLength, got: {other}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn debug_does_not_leak_key() {
|
||||||
|
let dk = derive_master_key("secret", None).unwrap();
|
||||||
|
let debug = format!("{dk:?}");
|
||||||
|
assert!(debug.contains("REDACTED"), "Debug output must redact key material");
|
||||||
|
assert!(!debug.contains("secret"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
//! OS keychain integration for secure credential storage.
|
||||||
|
//!
|
||||||
|
//! Abstracts platform-specific keychain backends:
|
||||||
|
//!
|
||||||
|
//! | Platform | Backend |
|
||||||
|
//! |----------|----------------------------------------------|
|
||||||
|
//! | Windows | Windows Credential Manager (DPAPI) |
|
||||||
|
//! | Linux | SecretService D-Bus API (libsecret) |
|
||||||
|
//! | macOS | macOS Keychain (Security framework) |
|
||||||
|
//! | Android | **Not supported here** — use Flutter KeyStore bridge |
|
||||||
|
//!
|
||||||
|
//! This module is compiled only on non-Android targets.
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
mod platform {
|
||||||
|
use keyring::Entry;
|
||||||
|
|
||||||
|
use crate::error::{CryptoError, Result};
|
||||||
|
|
||||||
|
/// Default service identifier used when none is provided.
|
||||||
|
pub const DEFAULT_SERVICE: &str = "com.chronogrid.p2p";
|
||||||
|
|
||||||
|
/// Default account identifier used when none is provided.
|
||||||
|
pub const DEFAULT_ACCOUNT: &str = "master_key";
|
||||||
|
|
||||||
|
/// Stores raw key material in the OS credential store.
|
||||||
|
///
|
||||||
|
/// The bytes are Base64-encoded before storage because some credential
|
||||||
|
/// backends only support UTF-8 string payloads.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `service` – Logical service name (e.g. `"com.chronogrid.p2p"`).
|
||||||
|
/// * `account` – Account / label for the credential.
|
||||||
|
/// * `key_material` – Raw bytes to store (typically 32-byte master key + 16-byte salt).
|
||||||
|
pub fn store_credential(
|
||||||
|
service: &str,
|
||||||
|
account: &str,
|
||||||
|
key_material: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let entry = Entry::new(service, account)
|
||||||
|
.map_err(|e| CryptoError::Keychain(e.to_string()))?;
|
||||||
|
|
||||||
|
// Encode as Base64 for credential-manager compatibility.
|
||||||
|
use base64_encode::encode;
|
||||||
|
let encoded = encode(key_material);
|
||||||
|
|
||||||
|
entry
|
||||||
|
.set_password(&encoded)
|
||||||
|
.map_err(|e| CryptoError::Keychain(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves raw key material from the OS credential store.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`CryptoError::CredentialNotFound`] when no matching entry exists.
|
||||||
|
pub fn retrieve_credential(service: &str, account: &str) -> Result<Vec<u8>> {
|
||||||
|
let entry = Entry::new(service, account)
|
||||||
|
.map_err(|e| CryptoError::Keychain(e.to_string()))?;
|
||||||
|
|
||||||
|
let encoded = entry.get_password().map_err(|e| {
|
||||||
|
// Distinguish "not found" from other failures.
|
||||||
|
let msg = e.to_string();
|
||||||
|
if msg.contains("No matching entry")
|
||||||
|
|| msg.contains("not found")
|
||||||
|
|| msg.contains("NoEntry")
|
||||||
|
{
|
||||||
|
CryptoError::CredentialNotFound {
|
||||||
|
service: service.to_owned(),
|
||||||
|
account: account.to_owned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CryptoError::Keychain(msg)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
use base64_decode::decode;
|
||||||
|
let bytes = decode(&encoded)
|
||||||
|
.map_err(|e| CryptoError::Internal(format!("base64 decode failed: {e}")))?;
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a credential from the OS credential store.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` even if the credential did not exist (idempotent).
|
||||||
|
pub fn delete_credential(service: &str, account: &str) -> Result<()> {
|
||||||
|
let entry = Entry::new(service, account)
|
||||||
|
.map_err(|e| CryptoError::Keychain(e.to_string()))?;
|
||||||
|
|
||||||
|
match entry.delete_credential() {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
let msg = e.to_string();
|
||||||
|
// Treat "not found" as a no-op for idempotency.
|
||||||
|
if msg.contains("No matching entry")
|
||||||
|
|| msg.contains("not found")
|
||||||
|
|| msg.contains("NoEntry")
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CryptoError::Keychain(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Inline Base64 helpers (no extra dependency needed) ────────────────
|
||||||
|
|
||||||
|
mod base64_encode {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
const CHARS: &[u8; 64] =
|
||||||
|
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
pub fn encode(input: &[u8]) -> String {
|
||||||
|
let mut out = String::with_capacity((input.len() + 2) / 3 * 4);
|
||||||
|
for chunk in input.chunks(3) {
|
||||||
|
let b0 = chunk[0] as u32;
|
||||||
|
let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
|
||||||
|
let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
|
||||||
|
let triple = (b0 << 16) | (b1 << 8) | b2;
|
||||||
|
|
||||||
|
let _ = write!(out, "{}", CHARS[((triple >> 18) & 0x3F) as usize] as char);
|
||||||
|
let _ = write!(out, "{}", CHARS[((triple >> 12) & 0x3F) as usize] as char);
|
||||||
|
if chunk.len() > 1 {
|
||||||
|
let _ = write!(out, "{}", CHARS[((triple >> 6) & 0x3F) as usize] as char);
|
||||||
|
} else {
|
||||||
|
out.push('=');
|
||||||
|
}
|
||||||
|
if chunk.len() > 2 {
|
||||||
|
let _ = write!(out, "{}", CHARS[(triple & 0x3F) as usize] as char);
|
||||||
|
} else {
|
||||||
|
out.push('=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod base64_decode {
|
||||||
|
pub fn decode(input: &str) -> std::result::Result<Vec<u8>, String> {
|
||||||
|
let input = input.trim_end_matches('=');
|
||||||
|
let mut out = Vec::with_capacity(input.len() * 3 / 4);
|
||||||
|
|
||||||
|
let val = |c: u8| -> std::result::Result<u32, String> {
|
||||||
|
match c {
|
||||||
|
b'A'..=b'Z' => Ok((c - b'A') as u32),
|
||||||
|
b'a'..=b'z' => Ok((c - b'a' + 26) as u32),
|
||||||
|
b'0'..=b'9' => Ok((c - b'0' + 52) as u32),
|
||||||
|
b'+' => Ok(62),
|
||||||
|
b'/' => Ok(63),
|
||||||
|
_ => Err(format!("invalid base64 char: {c}")),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = input.as_bytes();
|
||||||
|
let mut i = 0;
|
||||||
|
while i < bytes.len() {
|
||||||
|
let remaining = bytes.len() - i;
|
||||||
|
if remaining >= 4 {
|
||||||
|
let n = (val(bytes[i])? << 18)
|
||||||
|
| (val(bytes[i + 1])? << 12)
|
||||||
|
| (val(bytes[i + 2])? << 6)
|
||||||
|
| val(bytes[i + 3])?;
|
||||||
|
out.push((n >> 16) as u8);
|
||||||
|
out.push((n >> 8 & 0xFF) as u8);
|
||||||
|
out.push((n & 0xFF) as u8);
|
||||||
|
i += 4;
|
||||||
|
} else if remaining == 3 {
|
||||||
|
let n = (val(bytes[i])? << 18)
|
||||||
|
| (val(bytes[i + 1])? << 12)
|
||||||
|
| (val(bytes[i + 2])? << 6);
|
||||||
|
out.push((n >> 16) as u8);
|
||||||
|
out.push((n >> 8 & 0xFF) as u8);
|
||||||
|
i += 3;
|
||||||
|
} else if remaining == 2 {
|
||||||
|
let n = (val(bytes[i])? << 18) | (val(bytes[i + 1])? << 12);
|
||||||
|
out.push((n >> 16) as u8);
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::base64_decode::decode;
|
||||||
|
use super::base64_encode::encode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_round_trip() {
|
||||||
|
let original = b"hello, chronogrid crypto!";
|
||||||
|
let encoded = encode(original);
|
||||||
|
let decoded = decode(&encoded).expect("decode should succeed");
|
||||||
|
assert_eq!(decoded, original);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_round_trip_32_bytes() {
|
||||||
|
let original: Vec<u8> = (0..32).collect();
|
||||||
|
let encoded = encode(&original);
|
||||||
|
let decoded = decode(&encoded).expect("decode should succeed");
|
||||||
|
assert_eq!(decoded, original);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_empty() {
|
||||||
|
assert_eq!(encode(b""), "");
|
||||||
|
assert_eq!(decode("").unwrap(), Vec::<u8>::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export platform items at crate level (non-Android only).
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
pub use platform::*;
|
||||||
|
|
@ -1,12 +1,43 @@
|
||||||
//! # chronogrid_crypto
|
//! # chronogrid_crypto
|
||||||
//!
|
//!
|
||||||
//! Cryptographic primitives for ChronoGrid P2P.
|
//! Cryptographic primitives for **ChronoGrid P2P**.
|
||||||
//! Handles Argon2id master key derivation and OS keychain bindings.
|
//!
|
||||||
|
//! This crate provides two core capabilities:
|
||||||
|
//!
|
||||||
|
//! 1. **Argon2id master-key derivation** ([`kdf`]) — derives a 256-bit key from a
|
||||||
|
//! user passphrase using the parameters specified in
|
||||||
|
//! `docs/ARCHITECTURE/02-security-and-crypto.md`.
|
||||||
|
//!
|
||||||
|
//! 2. **OS keychain integration** ([`keychain`]) — stores and retrieves encrypted
|
||||||
|
//! key material via platform-native credential managers (Windows Credential
|
||||||
|
//! Manager / DPAPI, Linux SecretService / libsecret). Android credential
|
||||||
|
//! management is delegated to the Flutter KeyStore bridge.
|
||||||
|
//!
|
||||||
|
//! ## Quick Start
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use chronogrid_crypto::{kdf, keychain};
|
||||||
|
//!
|
||||||
|
//! // Derive a master key from a passphrase
|
||||||
|
//! let derived = kdf::derive_master_key("my secret passphrase", None)
|
||||||
|
//! .expect("key derivation failed");
|
||||||
|
//!
|
||||||
|
//! // Persist the key + salt in the OS keychain
|
||||||
|
//! let mut combined = Vec::with_capacity(48);
|
||||||
|
//! combined.extend_from_slice(derived.key());
|
||||||
|
//! combined.extend_from_slice(derived.salt());
|
||||||
|
//!
|
||||||
|
//! keychain::store_credential(
|
||||||
|
//! keychain::DEFAULT_SERVICE,
|
||||||
|
//! keychain::DEFAULT_ACCOUNT,
|
||||||
|
//! &combined,
|
||||||
|
//! ).expect("keychain store failed");
|
||||||
|
//! ```
|
||||||
|
|
||||||
#[cfg(test)]
|
pub mod error;
|
||||||
mod tests {
|
pub mod kdf;
|
||||||
#[test]
|
pub mod keychain;
|
||||||
fn it_works() {
|
|
||||||
assert!(true);
|
// Re-export key public types at crate root for ergonomic imports.
|
||||||
}
|
pub use error::{CryptoError, Result};
|
||||||
}
|
pub use kdf::{derive_master_key, generate_salt, DerivedKey, DERIVED_KEY_LEN, SALT_LEN};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# ADR-0002: chronogrid_crypto – Argon2id KDF & OS Keychain Strategy
|
||||||
|
|
||||||
|
* **Status**: Accepted
|
||||||
|
* **Date**: 2026-08-09
|
||||||
|
* **Deciders**: Michael Kell
|
||||||
|
|
||||||
|
## Context & Problem Statement
|
||||||
|
|
||||||
|
Task 1.2 requires implementing a `chronogrid_crypto` crate that provides:
|
||||||
|
1. A master-key derivation function using Argon2id with parameters mandated by `docs/ARCHITECTURE/02-security-and-crypto.md`.
|
||||||
|
2. Cross-platform OS keychain bindings for secure credential persistence (Windows, Linux). Android is deferred to the Flutter KeyStore bridge.
|
||||||
|
|
||||||
|
## Options Considered
|
||||||
|
|
||||||
|
1. **Hand-roll Argon2id via raw `blake2` + memory-hard loop** — maximum control but high risk of implementation bugs in a security-critical path.
|
||||||
|
2. **Use the `argon2` crate (RustCrypto)** — well-audited, widely used, supports Argon2id with configurable parameters out of the box.
|
||||||
|
3. **Use `ring` or `sodiumoxide`** — neither provides Argon2id natively; would still need the `argon2` crate.
|
||||||
|
|
||||||
|
For keychain:
|
||||||
|
1. **Direct Win32 / libsecret FFI** — full control, but significant per-platform maintenance burden.
|
||||||
|
2. **`keyring` crate** — abstracts Windows Credential Manager, Linux SecretService, and macOS Keychain behind a single API.
|
||||||
|
|
||||||
|
## Decision Outcome
|
||||||
|
|
||||||
|
**Chosen Options:**
|
||||||
|
- `argon2` crate v0.5 (RustCrypto) for KDF — spec-compliant, zero-copy output, and widely audited.
|
||||||
|
- `keyring` crate v3 for cross-platform OS keychain — minimal code, covers Windows + Linux targets. Android is excluded via `cfg(not(target_os = "android"))` and will be handled by the Flutter KeyStore bridge.
|
||||||
|
|
||||||
|
**Key design choices:**
|
||||||
|
- Argon2id parameters: memory = 65 536 KiB (64 MiB), time_cost = 3, parallelism = 4, salt = 16 bytes, output = 32 bytes.
|
||||||
|
- `DerivedKey` struct wraps key + salt and implements `Zeroize + Drop` for automatic memory scrubbing.
|
||||||
|
- `Debug` impl on `DerivedKey` redacts key material to prevent accidental log leaks.
|
||||||
|
- Raw bytes are Base64-encoded before keychain insertion for UTF-8 credential backend compatibility, using inline encode/decode helpers (no extra dependency).
|
||||||
|
|
||||||
|
## Consequences & Trade-offs
|
||||||
|
|
||||||
|
* **Positive**: Spec-compliant crypto with secure memory handling; single cross-platform keychain API; 8 unit tests + 1 doc-test provide baseline confidence.
|
||||||
|
* **Negative**: `keyring` v3 does not support Android — Android keychain must be wired separately via Flutter platform channels (planned for Task 1.5). Inline Base64 is minimal but could be replaced by `base64` crate if other crates need it later.
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
## Status: Ready for Agent Execution
|
## Status: Ready for Agent Execution
|
||||||
|
|
||||||
- [x] **Task 1.1**: Initialize Rust Cargo Workspace in crates/ and configure root Cargo.toml.
|
- [x] **Task 1.1**: Initialize Rust Cargo Workspace in crates/ and configure root Cargo.toml.
|
||||||
- [ ] **Task 1.2**: Implement chronogrid_crypto crate with Argon2id master key derivation and OS keychain bindings.
|
- [x] **Task 1.2**: Implement chronogrid_crypto crate with Argon2id master key derivation and OS keychain bindings.
|
||||||
- [ ] **Task 1.3**: Implement chronogrid_core database initializers using SQLCipher 4.x matching docs/API_CONTRACTS/schema.sql.
|
- [ ] **Task 1.3**: Implement chronogrid_core database initializers using SQLCipher 4.x matching docs/API_CONTRACTS/schema.sql.
|
||||||
- [ ] **Task 1.4**: Integrate Automerge Rust crate into chronogrid_core for task and note CRDT register mutations.
|
- [ ] **Task 1.4**: Integrate Automerge Rust crate into chronogrid_core for task and note CRDT register mutations.
|
||||||
- [ ] **Task 1.5**: Generate lutter_rust_bridge FFI bindings bridging Rust storage methods to Dart.
|
- [ ] **Task 1.5**: Generate lutter_rust_bridge FFI bindings bridging Rust storage methods to Dart.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"rustc_fingerprint":7332174474168670565,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\mtkel\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_primitive_alignment=\"128\"\ntarget_has_atomic_primitive_alignment=\"16\"\ntarget_has_atomic_primitive_alignment=\"32\"\ntarget_has_atomic_primitive_alignment=\"64\"\ntarget_has_atomic_primitive_alignment=\"8\"\ntarget_has_atomic_primitive_alignment=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"6487759729724009271":{"success":true,"status":"","code":0,"stdout":"rustc 1.97.1 (8bab26f4f 2026-07-14)\nbinary: rustc\ncommit-hash: 8bab26f4f68e0e26f0bb7960be334d5b520ea452\ncommit-date: 2026-07-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.97.1\nLLVM version: 22.1.6\n","stderr":""}},"successes":{}}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Signature: 8a477f597d28d172789f06886806bc55
|
|
||||||
# This file is a cache directory tag created by cargo.
|
|
||||||
# For information about cache directory tags see https://bford.info/cachedir/
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
This file has an mtime of when this was started.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
e14ca8334710f7f6
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"rustc":3720210673988096810,"features":"[]","declared_features":"[]","target":16581365484989365424,"profile":17672942494452627365,"path":16933786149472085738,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\chronogrid_ai-866b27a55c7f8eb2\\dep-lib-chronogrid_ai","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
This file has an mtime of when this was started.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
2341cb588ef75a5d
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"rustc":3720210673988096810,"features":"[]","declared_features":"[]","target":11075585498958225338,"profile":17672942494452627365,"path":14862345418060161617,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\chronogrid_core-4b25f3369283ba7e\\dep-lib-chronogrid_core","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
This file has an mtime of when this was started.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
8f4ec8434a3e5c1b
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"rustc":3720210673988096810,"features":"[]","declared_features":"[]","target":610712738496679682,"profile":17672942494452627365,"path":2818304346370217794,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\chronogrid_crypto-2743a56e71ece37d\\dep-lib-chronogrid_crypto","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
|
|
||||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
This file has an mtime of when this was started.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
50ffcf0c1a7b99c8
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"rustc":3720210673988096810,"features":"[]","declared_features":"[]","target":8139598702224368756,"profile":17672942494452627365,"path":17680580845150480923,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\chronogrid_net-e3495be9d1f94e7e\\dep-lib-chronogrid_net","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
C:\programming\chronogrid\target\debug\deps\chronogrid_ai-866b27a55c7f8eb2.d: crates\chronogrid_ai\src\lib.rs
|
|
||||||
|
|
||||||
C:\programming\chronogrid\target\debug\deps\libchronogrid_ai-866b27a55c7f8eb2.rmeta: crates\chronogrid_ai\src\lib.rs
|
|
||||||
|
|
||||||
crates\chronogrid_ai\src\lib.rs:
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
C:\programming\chronogrid\target\debug\deps\chronogrid_core-4b25f3369283ba7e.d: crates\chronogrid_core\src\lib.rs
|
|
||||||
|
|
||||||
C:\programming\chronogrid\target\debug\deps\libchronogrid_core-4b25f3369283ba7e.rmeta: crates\chronogrid_core\src\lib.rs
|
|
||||||
|
|
||||||
crates\chronogrid_core\src\lib.rs:
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
C:\programming\chronogrid\target\debug\deps\chronogrid_crypto-2743a56e71ece37d.d: crates\chronogrid_crypto\src\lib.rs
|
|
||||||
|
|
||||||
C:\programming\chronogrid\target\debug\deps\libchronogrid_crypto-2743a56e71ece37d.rmeta: crates\chronogrid_crypto\src\lib.rs
|
|
||||||
|
|
||||||
crates\chronogrid_crypto\src\lib.rs:
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
C:\programming\chronogrid\target\debug\deps\chronogrid_net-e3495be9d1f94e7e.d: crates\chronogrid_net\src\lib.rs
|
|
||||||
|
|
||||||
C:\programming\chronogrid\target\debug\deps\libchronogrid_net-e3495be9d1f94e7e.rmeta: crates\chronogrid_net\src\lib.rs
|
|
||||||
|
|
||||||
crates\chronogrid_net\src\lib.rs:
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue