feat(core): length-prefixed Argon2 input + NFC + Zeroize (audit H1, H2)
derive_master_key now: - length-prefixes passphrase and image_secret to eliminate concatenation ambiguity (H1) - normalizes passphrase to UTF-8 NFC before hashing - returns Zeroizing<[u8; 32]> so the master key is wiped on drop (H2) - wraps the intermediate password buffer in Zeroizing for the same reason Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
607
Cargo.lock
generated
607
Cargo.lock
generated
@@ -18,6 +18,24 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
@@ -106,6 +124,12 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@@ -129,6 +153,41 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bip39"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"serde",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b"
|
||||
dependencies = [
|
||||
"hex-conservative",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
@@ -217,6 +276,20 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -289,6 +362,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[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"
|
||||
@@ -367,6 +446,15 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -409,6 +497,17 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
@@ -434,6 +533,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -450,6 +555,17 @@ version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
version = "0.2.6"
|
||||
@@ -501,6 +617,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
@@ -581,6 +706,15 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-conservative"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
@@ -590,6 +724,112 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idfoto-cli"
|
||||
version = "0.1.0"
|
||||
@@ -605,6 +845,7 @@ dependencies = [
|
||||
"rpassword",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -612,14 +853,22 @@ name = "idfoto-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"bip39",
|
||||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"ed25519-dalek",
|
||||
"getrandom",
|
||||
"hex",
|
||||
"image",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"unicode-normalization",
|
||||
"url",
|
||||
"zeroize",
|
||||
"zxcvbn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -630,6 +879,7 @@ dependencies = [
|
||||
"getrandom",
|
||||
"hmac",
|
||||
"idfoto-core",
|
||||
"image",
|
||||
"js-sys",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
@@ -637,6 +887,27 @@ dependencies = [
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
@@ -668,6 +939,15 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -686,6 +966,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.184"
|
||||
@@ -713,6 +999,12 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -773,6 +1065,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -966,6 +1264,21 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -1055,6 +1368,35 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "5.0.1"
|
||||
@@ -1222,6 +1564,12 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -1245,6 +1593,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -1299,6 +1658,50 @@ dependencies = [
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@@ -1311,6 +1714,15 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
@@ -1321,6 +1733,25 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
@@ -1443,6 +1874,16 @@ version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23cda5ecc67248c48d3e705d3e03e00af905769b78b9d2a1678b663b8b9d4472"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
@@ -1480,12 +1921,65 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@@ -1635,6 +2129,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
@@ -1652,6 +2152,29 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
@@ -1672,11 +2195,79 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
@@ -1698,3 +2289,19 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zxcvbn"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad76e35b00ad53688d6b90c431cabe3cbf51f7a4a154739e04b63004ab1c736c"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
@@ -20,3 +20,4 @@ ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
zeroize = "1"
|
||||
|
||||
@@ -43,6 +43,7 @@ use idfoto_core::{
|
||||
decrypt_entry, decrypt_manifest, encrypt_entry, encrypt_manifest, generate_entry_id,
|
||||
Entry, KdfParams, Manifest, ManifestEntry,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -201,7 +202,7 @@ fn get_image_path() -> Result<PathBuf> {
|
||||
/// 2. Read and decode the reference JPEG, extracting the steganographic secret.
|
||||
/// 3. Load the vault salt and KDF params.
|
||||
/// 4. Derive the master key via Argon2id(passphrase || image_secret, salt).
|
||||
fn unlock(image_path: &PathBuf) -> Result<[u8; 32]> {
|
||||
fn unlock(image_path: &PathBuf) -> Result<Zeroizing<[u8; 32]>> {
|
||||
let passphrase = rpassword::prompt_password_stderr("Passphrase: ").context("failed to read passphrase")?;
|
||||
|
||||
let jpeg_data = fs::read(image_path).context("failed to read reference image")?;
|
||||
@@ -389,7 +390,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
|
||||
|
||||
// 10. Encrypt empty manifest
|
||||
let manifest = Manifest::new();
|
||||
let manifest_enc = encrypt_manifest(&master_key, &manifest).context("failed to encrypt manifest")?;
|
||||
let manifest_enc = encrypt_manifest(&*master_key, &manifest).context("failed to encrypt manifest")?;
|
||||
fs::write(vault_dir().join("manifest.enc"), manifest_enc)
|
||||
.context("failed to write manifest.enc")?;
|
||||
|
||||
@@ -463,14 +464,14 @@ fn cmd_add() -> Result<()> {
|
||||
};
|
||||
|
||||
let entry_id = generate_entry_id();
|
||||
let encrypted = encrypt_entry(&master_key, &entry).context("failed to encrypt entry")?;
|
||||
let encrypted = encrypt_entry(&*master_key, &entry).context("failed to encrypt entry")?;
|
||||
fs::write(
|
||||
vault_dir().join("entries").join(format!("{}.enc", entry_id)),
|
||||
encrypted,
|
||||
)
|
||||
.context("failed to write entry file")?;
|
||||
|
||||
let mut manifest = read_manifest(&master_key)?;
|
||||
let mut manifest = read_manifest(&*master_key)?;
|
||||
manifest.add_entry(
|
||||
entry_id.clone(),
|
||||
ManifestEntry {
|
||||
@@ -481,7 +482,7 @@ fn cmd_add() -> Result<()> {
|
||||
updated_at: now,
|
||||
},
|
||||
);
|
||||
write_manifest(&master_key, &manifest)?;
|
||||
write_manifest(&*master_key, &manifest)?;
|
||||
|
||||
git_commit(&format!("feat: add entry '{}'", name))?;
|
||||
eprintln!("Entry '{}' added (id: {})", name, entry_id);
|
||||
@@ -534,12 +535,12 @@ fn cmd_get(query: String) -> Result<()> {
|
||||
let image_path = get_image_path()?;
|
||||
let master_key = unlock(&image_path)?;
|
||||
|
||||
let manifest = read_manifest(&master_key)?;
|
||||
let manifest = read_manifest(&*master_key)?;
|
||||
let (entry_id, _) = search_and_select(&manifest, &query)?;
|
||||
|
||||
let data = fs::read(vault_dir().join("entries").join(format!("{}.enc", entry_id)))
|
||||
.context("failed to read entry file")?;
|
||||
let entry = decrypt_entry(&master_key, &data).context("failed to decrypt entry")?;
|
||||
let entry = decrypt_entry(&*master_key, &data).context("failed to decrypt entry")?;
|
||||
|
||||
println!("Name: {}", entry.name);
|
||||
println!(
|
||||
@@ -595,7 +596,7 @@ fn cmd_list() -> Result<()> {
|
||||
let image_path = get_image_path()?;
|
||||
let master_key = unlock(&image_path)?;
|
||||
|
||||
let manifest = read_manifest(&master_key)?;
|
||||
let manifest = read_manifest(&*master_key)?;
|
||||
|
||||
let mut entries: Vec<_> = manifest.entries.iter().collect();
|
||||
entries.sort_by(|a, b| a.1.name.to_lowercase().cmp(&b.1.name.to_lowercase()));
|
||||
@@ -626,12 +627,12 @@ fn cmd_edit(query: String) -> Result<()> {
|
||||
let image_path = get_image_path()?;
|
||||
let master_key = unlock(&image_path)?;
|
||||
|
||||
let manifest = read_manifest(&master_key)?;
|
||||
let manifest = read_manifest(&*master_key)?;
|
||||
let (entry_id, _) = search_and_select(&manifest, &query)?;
|
||||
|
||||
let data = fs::read(vault_dir().join("entries").join(format!("{}.enc", entry_id)))
|
||||
.context("failed to read entry file")?;
|
||||
let entry = decrypt_entry(&master_key, &data).context("failed to decrypt entry")?;
|
||||
let entry = decrypt_entry(&*master_key, &data).context("failed to decrypt entry")?;
|
||||
|
||||
eprintln!("Editing '{}' (Enter to keep current value)", entry.name);
|
||||
|
||||
@@ -667,14 +668,14 @@ fn cmd_edit(query: String) -> Result<()> {
|
||||
updated_at: now.clone(),
|
||||
};
|
||||
|
||||
let encrypted = encrypt_entry(&master_key, &updated_entry).context("failed to encrypt entry")?;
|
||||
let encrypted = encrypt_entry(&*master_key, &updated_entry).context("failed to encrypt entry")?;
|
||||
fs::write(
|
||||
vault_dir().join("entries").join(format!("{}.enc", entry_id)),
|
||||
encrypted,
|
||||
)
|
||||
.context("failed to write entry file")?;
|
||||
|
||||
let mut manifest = read_manifest(&master_key)?;
|
||||
let mut manifest = read_manifest(&*master_key)?;
|
||||
manifest.add_entry(
|
||||
entry_id,
|
||||
ManifestEntry {
|
||||
@@ -685,7 +686,7 @@ fn cmd_edit(query: String) -> Result<()> {
|
||||
updated_at: now,
|
||||
},
|
||||
);
|
||||
write_manifest(&master_key, &manifest)?;
|
||||
write_manifest(&*master_key, &manifest)?;
|
||||
|
||||
git_commit(&format!("feat: edit entry '{}'", name))?;
|
||||
eprintln!("Entry '{}' updated.", name);
|
||||
@@ -701,7 +702,7 @@ fn cmd_rm(query: String) -> Result<()> {
|
||||
let image_path = get_image_path()?;
|
||||
let master_key = unlock(&image_path)?;
|
||||
|
||||
let manifest = read_manifest(&master_key)?;
|
||||
let manifest = read_manifest(&*master_key)?;
|
||||
let (entry_id, entry) = search_and_select(&manifest, &query)?;
|
||||
|
||||
let confirm = prompt(&format!("Delete '{}' (id: {})? [y/N]", entry.name, entry_id))?;
|
||||
@@ -717,9 +718,9 @@ fn cmd_rm(query: String) -> Result<()> {
|
||||
fs::remove_file(&entry_path).context("failed to remove entry file")?;
|
||||
}
|
||||
|
||||
let mut manifest = read_manifest(&master_key)?;
|
||||
let mut manifest = read_manifest(&*master_key)?;
|
||||
manifest.remove_entry(&entry_id);
|
||||
write_manifest(&master_key, &manifest)?;
|
||||
write_manifest(&*master_key, &manifest)?;
|
||||
|
||||
git_commit(&format!("feat: remove entry '{}'", entry.name))?;
|
||||
eprintln!("Entry '{}' removed.", entry.name);
|
||||
|
||||
@@ -50,6 +50,8 @@ use chacha20poly1305::{
|
||||
};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::error::{IdfotoError, Result};
|
||||
|
||||
@@ -207,7 +209,7 @@ pub fn derive_master_key(
|
||||
image_secret: &[u8; 32],
|
||||
salt: &[u8; 32],
|
||||
params: &KdfParams,
|
||||
) -> Result<[u8; 32]> {
|
||||
) -> Result<Zeroizing<[u8; 32]>> {
|
||||
let argon2_params = Params::new(
|
||||
params.argon2_m,
|
||||
params.argon2_t,
|
||||
@@ -218,17 +220,24 @@ pub fn derive_master_key(
|
||||
|
||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
|
||||
|
||||
// Concatenate passphrase + image_secret as the password input.
|
||||
// This ensures both factors contribute to the derived key: knowing only
|
||||
// the passphrase (without the reference image) or only the image secret
|
||||
// (without the passphrase) is insufficient to derive the correct master key.
|
||||
let mut password = Vec::with_capacity(passphrase.len() + 32);
|
||||
password.extend_from_slice(passphrase);
|
||||
// Normalize passphrase to NFC. Invalid UTF-8 bytes pass through unchanged.
|
||||
let nfc_passphrase: Vec<u8> = match std::str::from_utf8(passphrase) {
|
||||
Ok(s) => s.nfc().collect::<String>().into_bytes(),
|
||||
Err(_) => passphrase.to_vec(),
|
||||
};
|
||||
|
||||
// Length-prefixed concatenation: [u64_be(len(passphrase))][passphrase]
|
||||
// [u64_be(32)][image_secret]
|
||||
// Eliminates the (passphrase, image_secret) boundary ambiguity (audit H1).
|
||||
let mut password = Zeroizing::new(Vec::with_capacity(8 + nfc_passphrase.len() + 8 + 32));
|
||||
password.extend_from_slice(&(nfc_passphrase.len() as u64).to_be_bytes());
|
||||
password.extend_from_slice(&nfc_passphrase);
|
||||
password.extend_from_slice(&32u64.to_be_bytes());
|
||||
password.extend_from_slice(image_secret);
|
||||
|
||||
let mut output = [0u8; 32];
|
||||
let mut output = Zeroizing::new([0u8; 32]);
|
||||
argon2
|
||||
.hash_password_into(&password, salt, &mut output)
|
||||
.hash_password_into(password.as_slice(), salt, output.as_mut())
|
||||
.map_err(|e| IdfotoError::Kdf(e.to_string()))?;
|
||||
|
||||
Ok(output)
|
||||
@@ -256,7 +265,7 @@ mod tests {
|
||||
let key1 = derive_master_key(passphrase, &image_secret, &salt, ¶ms).unwrap();
|
||||
let key2 = derive_master_key(passphrase, &image_secret, &salt, ¶ms).unwrap();
|
||||
|
||||
assert_eq!(key1, key2);
|
||||
assert_eq!(*key1, *key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -268,7 +277,7 @@ mod tests {
|
||||
let key1 = derive_master_key(b"passphrase-one", &image_secret, &salt, ¶ms).unwrap();
|
||||
let key2 = derive_master_key(b"passphrase-two", &image_secret, &salt, ¶ms).unwrap();
|
||||
|
||||
assert_ne!(key1, key2);
|
||||
assert_ne!(*key1, *key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -283,7 +292,7 @@ mod tests {
|
||||
let key1 = derive_master_key(passphrase, &image_secret1, &salt, ¶ms).unwrap();
|
||||
let key2 = derive_master_key(passphrase, &image_secret2, &salt, ¶ms).unwrap();
|
||||
|
||||
assert_ne!(key1, key2);
|
||||
assert_ne!(*key1, *key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -338,4 +347,51 @@ mod tests {
|
||||
// Version byte must be 0x01
|
||||
assert_eq!(ciphertext[0], 0x01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length_prefix_eliminates_concatenation_ambiguity() {
|
||||
// Without length-prefix: ("abc", [0x44, ...]) and ("abcD", [...]) could collide.
|
||||
// With length-prefix: distinct inputs always yield distinct keys.
|
||||
let salt = [0u8; 32];
|
||||
let params = fast_params();
|
||||
|
||||
// Pair A: passphrase "abc", image_secret starts with 0x44
|
||||
let mut img_a = [0u8; 32]; img_a[0] = 0x44;
|
||||
let key_a = derive_master_key(b"abc", &img_a, &salt, ¶ms).unwrap();
|
||||
|
||||
// Pair B: passphrase "abcD" (one extra char), image_secret starts with original byte 1
|
||||
let mut img_b = [0u8; 32]; img_b[0] = 0x44; // same image
|
||||
let key_b = derive_master_key(b"abcD", &img_b, &salt, ¶ms).unwrap();
|
||||
|
||||
// With length-prefix, the keys MUST differ.
|
||||
assert_ne!(*key_a, *key_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nfc_normalization_collapses_unicode_forms() {
|
||||
// "café" can be written as NFC (é = U+00E9) or NFD (e + U+0301).
|
||||
// Both must produce the same key after NFC normalization.
|
||||
let salt = [0u8; 32];
|
||||
let img = [0u8; 32];
|
||||
let params = fast_params();
|
||||
|
||||
let nfc = "caf\u{00e9}".as_bytes(); // é precomposed
|
||||
let nfd = "cafe\u{0301}".as_bytes(); // e + combining acute
|
||||
|
||||
let key_nfc = derive_master_key(nfc, &img, &salt, ¶ms).unwrap();
|
||||
let key_nfd = derive_master_key(nfd, &img, &salt, ¶ms).unwrap();
|
||||
|
||||
assert_eq!(*key_nfc, *key_nfd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn master_key_is_zeroized_on_drop() {
|
||||
// Smoke test: master_key returns a Zeroizing<[u8; 32]>, which compiles only if
|
||||
// we wrap correctly. The drop wipe is verified by the zeroize crate's tests.
|
||||
let salt = [0u8; 32];
|
||||
let img = [0u8; 32];
|
||||
let params = fast_params();
|
||||
let key: zeroize::Zeroizing<[u8; 32]> = derive_master_key(b"x", &img, &salt, ¶ms).unwrap();
|
||||
assert_eq!(key.len(), 32);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user