diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a4d386 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Rust build artifacts +/target/ +**/*.rs.bk + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/Cargo.lock b/Cargo.lock index 5243e34..847b69c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,60 @@ # It is not intended for manual editing. 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]] name = "chronogrid_ai" version = "0.1.0" @@ -13,7 +67,459 @@ version = "0.1.0" [[package]] name = "chronogrid_crypto" version = "0.1.0" +dependencies = [ + "argon2", + "keyring", + "rand", + "thiserror", + "zeroize", +] [[package]] name = "chronogrid_net" 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", +] diff --git a/DECISION_LOG.md b/DECISION_LOG.md index 65a317b..ebdfaa9 100644 --- a/DECISION_LOG.md +++ b/DECISION_LOG.md @@ -5,4 +5,15 @@ - **Status**: Accepted - **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. - \ No newline at end of file + +## [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. + diff --git a/crates/chronogrid_crypto/Cargo.toml b/crates/chronogrid_crypto/Cargo.toml index 3c3651f..fbf96a1 100644 --- a/crates/chronogrid_crypto/Cargo.toml +++ b/crates/chronogrid_crypto/Cargo.toml @@ -7,5 +7,13 @@ rust-version.workspace = true publish = false [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] diff --git a/crates/chronogrid_crypto/src/error.rs b/crates/chronogrid_crypto/src/error.rs new file mode 100644 index 0000000..2d5ff20 --- /dev/null +++ b/crates/chronogrid_crypto/src/error.rs @@ -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 = std::result::Result; diff --git a/crates/chronogrid_crypto/src/kdf.rs b/crates/chronogrid_crypto/src/kdf.rs new file mode 100644 index 0000000..ae62ac2 --- /dev/null +++ b/crates/chronogrid_crypto/src/kdf.rs @@ -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 { + // 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")); + } +} diff --git a/crates/chronogrid_crypto/src/keychain.rs b/crates/chronogrid_crypto/src/keychain.rs new file mode 100644 index 0000000..6826ced --- /dev/null +++ b/crates/chronogrid_crypto/src/keychain.rs @@ -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> { + 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, String> { + let input = input.trim_end_matches('='); + let mut out = Vec::with_capacity(input.len() * 3 / 4); + + let val = |c: u8| -> std::result::Result { + 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 = (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::::new()); + } + } +} + +// Re-export platform items at crate level (non-Android only). +#[cfg(not(target_os = "android"))] +pub use platform::*; diff --git a/crates/chronogrid_crypto/src/lib.rs b/crates/chronogrid_crypto/src/lib.rs index ea26eb5..32bb845 100644 --- a/crates/chronogrid_crypto/src/lib.rs +++ b/crates/chronogrid_crypto/src/lib.rs @@ -1,12 +1,43 @@ //! # chronogrid_crypto //! -//! Cryptographic primitives for ChronoGrid P2P. -//! Handles Argon2id master key derivation and OS keychain bindings. +//! Cryptographic primitives for **ChronoGrid P2P**. +//! +//! 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)] -mod tests { - #[test] - fn it_works() { - assert!(true); - } -} +pub mod error; +pub mod kdf; +pub mod keychain; + +// 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}; diff --git a/docs/ARCHITECTURE/ADR/0002-crypto-argon2id-keychain.md b/docs/ARCHITECTURE/ADR/0002-crypto-argon2id-keychain.md new file mode 100644 index 0000000..c87f9e9 --- /dev/null +++ b/docs/ARCHITECTURE/ADR/0002-crypto-argon2id-keychain.md @@ -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. diff --git a/docs/TASKS/phase_1_storage_and_crdt.md b/docs/TASKS/phase_1_storage_and_crdt.md index 7e0a051..bf87387 100644 --- a/docs/TASKS/phase_1_storage_and_crdt.md +++ b/docs/TASKS/phase_1_storage_and_crdt.md @@ -3,7 +3,7 @@ ## Status: Ready for Agent Execution - [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.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. diff --git a/target/.rustc_info.json b/target/.rustc_info.json deleted file mode 100644 index bffbb9a..0000000 --- a/target/.rustc_info.json +++ /dev/null @@ -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":{}} \ No newline at end of file diff --git a/target/CACHEDIR.TAG b/target/CACHEDIR.TAG deleted file mode 100644 index 20d7c31..0000000 --- a/target/CACHEDIR.TAG +++ /dev/null @@ -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/ diff --git a/target/debug/.cargo-build-lock b/target/debug/.cargo-build-lock deleted file mode 100644 index e69de29..0000000 diff --git a/target/debug/.cargo-lock b/target/debug/.cargo-lock deleted file mode 100644 index e69de29..0000000 diff --git a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/dep-lib-chronogrid_ai b/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/dep-lib-chronogrid_ai deleted file mode 100644 index 02bca30..0000000 Binary files a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/dep-lib-chronogrid_ai and /dev/null differ diff --git a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/invoked.timestamp b/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/invoked.timestamp deleted file mode 100644 index e00328d..0000000 --- a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/invoked.timestamp +++ /dev/null @@ -1 +0,0 @@ -This file has an mtime of when this was started. \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai b/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai deleted file mode 100644 index 50d70b5..0000000 --- a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai +++ /dev/null @@ -1 +0,0 @@ -e14ca8334710f7f6 \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai.json b/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai.json deleted file mode 100644 index 83292d4..0000000 --- a/target/debug/.fingerprint/chronogrid_ai-866b27a55c7f8eb2/lib-chronogrid_ai.json +++ /dev/null @@ -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} \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/dep-lib-chronogrid_core b/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/dep-lib-chronogrid_core deleted file mode 100644 index 02bca30..0000000 Binary files a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/dep-lib-chronogrid_core and /dev/null differ diff --git a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/invoked.timestamp b/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/invoked.timestamp deleted file mode 100644 index e00328d..0000000 --- a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/invoked.timestamp +++ /dev/null @@ -1 +0,0 @@ -This file has an mtime of when this was started. \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core b/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core deleted file mode 100644 index 8efd338..0000000 --- a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core +++ /dev/null @@ -1 +0,0 @@ -2341cb588ef75a5d \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core.json b/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core.json deleted file mode 100644 index a7988d9..0000000 --- a/target/debug/.fingerprint/chronogrid_core-4b25f3369283ba7e/lib-chronogrid_core.json +++ /dev/null @@ -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} \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/dep-lib-chronogrid_crypto b/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/dep-lib-chronogrid_crypto deleted file mode 100644 index 02bca30..0000000 Binary files a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/dep-lib-chronogrid_crypto and /dev/null differ diff --git a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/invoked.timestamp b/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/invoked.timestamp deleted file mode 100644 index e00328d..0000000 --- a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/invoked.timestamp +++ /dev/null @@ -1 +0,0 @@ -This file has an mtime of when this was started. \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto b/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto deleted file mode 100644 index c587177..0000000 --- a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto +++ /dev/null @@ -1 +0,0 @@ -8f4ec8434a3e5c1b \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto.json b/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto.json deleted file mode 100644 index d9d2718..0000000 --- a/target/debug/.fingerprint/chronogrid_crypto-2743a56e71ece37d/lib-chronogrid_crypto.json +++ /dev/null @@ -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} \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/dep-lib-chronogrid_net b/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/dep-lib-chronogrid_net deleted file mode 100644 index 02bca30..0000000 Binary files a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/dep-lib-chronogrid_net and /dev/null differ diff --git a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/invoked.timestamp b/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/invoked.timestamp deleted file mode 100644 index e00328d..0000000 --- a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/invoked.timestamp +++ /dev/null @@ -1 +0,0 @@ -This file has an mtime of when this was started. \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net b/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net deleted file mode 100644 index d79b717..0000000 --- a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net +++ /dev/null @@ -1 +0,0 @@ -50ffcf0c1a7b99c8 \ No newline at end of file diff --git a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net.json b/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net.json deleted file mode 100644 index ff83f3a..0000000 --- a/target/debug/.fingerprint/chronogrid_net-e3495be9d1f94e7e/lib-chronogrid_net.json +++ /dev/null @@ -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} \ No newline at end of file diff --git a/target/debug/deps/chronogrid_ai-866b27a55c7f8eb2.d b/target/debug/deps/chronogrid_ai-866b27a55c7f8eb2.d deleted file mode 100644 index bc549ea..0000000 --- a/target/debug/deps/chronogrid_ai-866b27a55c7f8eb2.d +++ /dev/null @@ -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: diff --git a/target/debug/deps/chronogrid_core-4b25f3369283ba7e.d b/target/debug/deps/chronogrid_core-4b25f3369283ba7e.d deleted file mode 100644 index 17ef7a4..0000000 --- a/target/debug/deps/chronogrid_core-4b25f3369283ba7e.d +++ /dev/null @@ -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: diff --git a/target/debug/deps/chronogrid_crypto-2743a56e71ece37d.d b/target/debug/deps/chronogrid_crypto-2743a56e71ece37d.d deleted file mode 100644 index 3fc769f..0000000 --- a/target/debug/deps/chronogrid_crypto-2743a56e71ece37d.d +++ /dev/null @@ -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: diff --git a/target/debug/deps/chronogrid_net-e3495be9d1f94e7e.d b/target/debug/deps/chronogrid_net-e3495be9d1f94e7e.d deleted file mode 100644 index ecdab69..0000000 --- a/target/debug/deps/chronogrid_net-e3495be9d1f94e7e.d +++ /dev/null @@ -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: diff --git a/target/debug/deps/libchronogrid_ai-866b27a55c7f8eb2.rmeta b/target/debug/deps/libchronogrid_ai-866b27a55c7f8eb2.rmeta deleted file mode 100644 index 3ad2fa2..0000000 Binary files a/target/debug/deps/libchronogrid_ai-866b27a55c7f8eb2.rmeta and /dev/null differ diff --git a/target/debug/deps/libchronogrid_core-4b25f3369283ba7e.rmeta b/target/debug/deps/libchronogrid_core-4b25f3369283ba7e.rmeta deleted file mode 100644 index 7236940..0000000 Binary files a/target/debug/deps/libchronogrid_core-4b25f3369283ba7e.rmeta and /dev/null differ diff --git a/target/debug/deps/libchronogrid_crypto-2743a56e71ece37d.rmeta b/target/debug/deps/libchronogrid_crypto-2743a56e71ece37d.rmeta deleted file mode 100644 index 1437da5..0000000 Binary files a/target/debug/deps/libchronogrid_crypto-2743a56e71ece37d.rmeta and /dev/null differ diff --git a/target/debug/deps/libchronogrid_net-e3495be9d1f94e7e.rmeta b/target/debug/deps/libchronogrid_net-e3495be9d1f94e7e.rmeta deleted file mode 100644 index 3099f44..0000000 Binary files a/target/debug/deps/libchronogrid_net-e3495be9d1f94e7e.rmeta and /dev/null differ diff --git a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/dep-graph.bin b/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/dep-graph.bin deleted file mode 100644 index 8e94d2b..0000000 Binary files a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/dep-graph.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/metadata.rmeta b/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/metadata.rmeta deleted file mode 100644 index 3ad2fa2..0000000 Binary files a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/metadata.rmeta and /dev/null differ diff --git a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/query-cache.bin b/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/query-cache.bin deleted file mode 100644 index 38628e0..0000000 Binary files a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/query-cache.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/work-products.bin b/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/work-products.bin deleted file mode 100644 index 4f769ac..0000000 Binary files a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0-0epgoeh8ku88a0dn63xx7hrnz/work-products.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0.lock b/target/debug/incremental/chronogrid_ai-00kqg8as75teg/s-hl79dsv8ja-0g58ob0.lock deleted file mode 100644 index e69de29..0000000 diff --git a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/dep-graph.bin b/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/dep-graph.bin deleted file mode 100644 index 37b33a1..0000000 Binary files a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/dep-graph.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/metadata.rmeta b/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/metadata.rmeta deleted file mode 100644 index 7236940..0000000 Binary files a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/metadata.rmeta and /dev/null differ diff --git a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/query-cache.bin b/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/query-cache.bin deleted file mode 100644 index 1d5ce8c..0000000 Binary files a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/query-cache.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/work-products.bin b/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/work-products.bin deleted file mode 100644 index 4f769ac..0000000 Binary files a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp-d9bd3jjjqm25wm6tafs2r2vnj/work-products.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp.lock b/target/debug/incremental/chronogrid_core-25wl2zu7tc4qc/s-hl79dsuzmm-00ggpwp.lock deleted file mode 100644 index e69de29..0000000 diff --git a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/dep-graph.bin b/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/dep-graph.bin deleted file mode 100644 index 3367c2b..0000000 Binary files a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/dep-graph.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/metadata.rmeta b/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/metadata.rmeta deleted file mode 100644 index 1437da5..0000000 Binary files a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/metadata.rmeta and /dev/null differ diff --git a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/query-cache.bin b/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/query-cache.bin deleted file mode 100644 index 6140aa3..0000000 Binary files a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/query-cache.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/work-products.bin b/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/work-products.bin deleted file mode 100644 index 4f769ac..0000000 Binary files a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4-ep33nnmy8je5jvu0aacp9makh/work-products.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4.lock b/target/debug/incremental/chronogrid_crypto-080ztx429suac/s-hl79dsvfnw-1d37dg4.lock deleted file mode 100644 index e69de29..0000000 diff --git a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/dep-graph.bin b/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/dep-graph.bin deleted file mode 100644 index ddf558d..0000000 Binary files a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/dep-graph.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/metadata.rmeta b/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/metadata.rmeta deleted file mode 100644 index 3099f44..0000000 Binary files a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/metadata.rmeta and /dev/null differ diff --git a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/query-cache.bin b/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/query-cache.bin deleted file mode 100644 index 7aba687..0000000 Binary files a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/query-cache.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/work-products.bin b/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/work-products.bin deleted file mode 100644 index 4f769ac..0000000 Binary files a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn-7ecux3zw5un4lsyldr24ixq8k/work-products.bin and /dev/null differ diff --git a/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn.lock b/target/debug/incremental/chronogrid_net-0e97jb7s258wr/s-hl79dsvk8o-0p6zmpn.lock deleted file mode 100644 index e69de29..0000000