From 174727fba6a42a1abf430212c76bc1439e856fa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:50:00 +0000 Subject: [PATCH 1/2] Bump the cargo group across 1 directory with 13 updates Bumps the cargo group with 13 updates in the / directory: | Package | From | To | | --- | --- | --- | | [serde_json](https://github.com/serde-rs/json) | `1.0.151` | `1.0.154` | | [gloo-net](https://github.com/rustwasm/gloo) | `0.6.0` | `0.7.0` | | [rusqlite](https://github.com/rusqlite/rusqlite) | `0.32.1` | `0.41.1` | | [uuid](https://github.com/uuid-rs/uuid) | `1.27.0` | `1.29.0` | | [base64](https://github.com/marshallpierce/rust-base64) | `0.22.1` | `0.23.1` | | [sha2](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.11.1` | | [tower-http](https://github.com/tower-rs/tower-http) | `0.6.11` | `0.7.1` | | [libc](https://github.com/rust-lang/libc) | `0.2.190` | `0.2.191` | | [aes-gcm](https://github.com/RustCrypto/AEADs) | `0.10.3` | `0.11.1` | | [hkdf](https://github.com/RustCrypto/KDFs) | `0.12.4` | `0.13.0` | | [rand](https://github.com/rust-random/rand) | `0.8.8` | `0.10.3` | | [zeroize](https://github.com/RustCrypto/utils) | `1.9.0` | `1.9.1` | | [bip39](https://github.com/rust-bitcoin/rust-bip39) | `2.2.2` | `3.0.0` | Updates `serde_json` from 1.0.151 to 1.0.154 - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.151...v1.0.154) Updates `gloo-net` from 0.6.0 to 0.7.0 - [Release notes](https://github.com/rustwasm/gloo/releases) - [Changelog](https://github.com/ranile/gloo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rustwasm/gloo/compare/0.6.0...gloo-net-v0.7.0) Updates `rusqlite` from 0.32.1 to 0.41.1 - [Release notes](https://github.com/rusqlite/rusqlite/releases) - [Changelog](https://github.com/rusqlite/rusqlite/blob/master/Changelog.md) - [Commits](https://github.com/rusqlite/rusqlite/compare/v0.32.1...v0.41.1) Updates `uuid` from 1.27.0 to 1.29.0 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.27.0...v1.29.0) Updates `base64` from 0.22.1 to 0.23.1 - [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md) - [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.1...v0.23.1) Updates `sha2` from 0.10.9 to 0.11.1 - [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.9...sha2-v0.11.1) Updates `tower-http` from 0.6.11 to 0.7.1 - [Release notes](https://github.com/tower-rs/tower-http/releases) - [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.11...tower-http-0.7.1) Updates `libc` from 0.2.190 to 0.2.191 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.191/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.190...0.2.191) Updates `aes-gcm` from 0.10.3 to 0.11.1 - [Commits](https://github.com/RustCrypto/AEADs/compare/aes-gcm-v0.10.3...aes-gcm-v0.11.1) Updates `hkdf` from 0.12.4 to 0.13.0 - [Commits](https://github.com/RustCrypto/KDFs/compare/hkdf-v0.12.4...hkdf-v0.13.0) Updates `rand` from 0.8.8 to 0.10.3 - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/rand/compare/0.8.8...0.10.3) Updates `zeroize` from 1.9.0 to 1.9.1 - [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.9.0...zeroize-v1.9.1) Updates `bip39` from 2.2.2 to 3.0.0 - [Changelog](https://github.com/rust-bitcoin/rust-bip39/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-bitcoin/rust-bip39/compare/v2.2.2...v3.0.0) --- updated-dependencies: - dependency-name: aes-gcm dependency-version: 0.11.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: base64 dependency-version: 0.23.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: bip39 dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: cargo - dependency-name: gloo-net dependency-version: 0.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: hkdf dependency-version: 0.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: libc dependency-version: 0.2.191 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo - dependency-name: rand dependency-version: 0.10.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: rusqlite dependency-version: 0.41.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: serde_json dependency-version: 1.0.154 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo - dependency-name: sha2 dependency-version: 0.11.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: tower-http dependency-version: 0.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: uuid dependency-version: 1.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo - dependency-name: zeroize dependency-version: 1.9.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo ... Signed-off-by: dependabot[bot] --- Cargo.lock | 354 +++++++++++++++++++++++++++++----------------- app/Cargo.toml | 2 +- server/Cargo.toml | 16 +-- 3 files changed, 233 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 348a4a8..233a8e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,49 +4,37 @@ version = 4 [[package]] name = "aead" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ - "crypto-common", - "generic-array", + "crypto-common 0.2.2", + "inout", ] [[package]] name = "aes" -version = "0.8.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ - "cfg-if", "cipher", - "cpufeatures 0.2.17", + "cpubits", + "cpufeatures 0.3.1", ] [[package]] name = "aes-gcm" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" dependencies = [ "aead", "aes", "cipher", "ctr", + "ctutils", "ghash", - "subtle", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", ] [[package]] @@ -207,9 +195,9 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bip39" -version = "2.2.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +checksum = "4559bb9225f8401dcea601cf8dc9ed81ea0dadd9cf7755fdefdd93ef7961e19e" dependencies = [ "bitcoin_hashes", "serde", @@ -240,6 +228,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -363,14 +360,21 @@ dependencies = [ [[package]] name = "cipher" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "crypto-common", + "block-buffer 0.12.1", + "crypto-common 0.2.2", "inout", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.8" @@ -381,6 +385,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-serialize" version = "0.7.2" @@ -538,6 +548,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -569,19 +585,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" +dependencies = [ + "cmov", +] + [[package]] name = "darling" version = "0.21.3" @@ -657,9 +692,20 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", - "subtle", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -854,7 +900,7 @@ dependencies = [ "futures", "futures-channel", "futures-util", - "gloo-net", + "gloo-net 0.6.0", "headers", "http", "http-body", @@ -1136,7 +1182,7 @@ name = "echo-app" version = "1.0.0" dependencies = [ "dioxus", - "gloo-net", + "gloo-net 0.7.0", "js-sys", "serde_json", "wasm-bindgen", @@ -1150,7 +1196,7 @@ dependencies = [ "aes-gcm", "async-stream", "axum", - "base64 0.22.1", + "base64 0.23.1", "bip39", "chrono", "dotenvy", @@ -1161,7 +1207,7 @@ dependencies = [ "http-body-util", "keyring", "libc", - "rand 0.8.8", + "rand 0.10.3", "reqwest", "rusqlite", "serde", @@ -1170,7 +1216,7 @@ dependencies = [ "tempfile", "tokio", "tower", - "tower-http", + "tower-http 0.7.1", "tracing", "tracing-subscriber", "url", @@ -1268,6 +1314,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1436,11 +1488,10 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "opaque-debug", "polyval", ] @@ -1453,7 +1504,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils", + "gloo-utils 0.2.0", "http", "js-sys", "pin-project", @@ -1465,6 +1516,27 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-net" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6420f887c48417e9e86c6cf61274eb231830cccc100e49613f7952e269a1fe1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.3.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 2.0.21", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-timers" version = "0.3.0" @@ -1490,6 +1562,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4202275d95a142fa209a1e35e91c250a710c5600731372cd3464a39ed01573d6" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "half" version = "2.7.1" @@ -1501,28 +1586,22 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" dependencies = [ - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -1560,20 +1639,20 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] @@ -1627,6 +1706,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.1" @@ -1829,7 +1917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", ] [[package]] @@ -1843,11 +1931,11 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -1976,9 +2064,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "libc" -version = "0.2.190" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libloading" @@ -1992,13 +2080,14 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3" dependencies = [ "cc", "openssl-sys", "pkg-config", + "sqlcipher-src", "vcpkg", ] @@ -2288,12 +2377,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl-src" version = "300.6.1+3.6.3" @@ -2379,13 +2462,12 @@ checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "polyval" -version = "0.6.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", + "cpubits", + "cpufeatures 0.3.1", "universal-hash", ] @@ -2536,24 +2618,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -2568,16 +2639,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[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 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -2588,15 +2649,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -2687,7 +2739,7 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower", - "tower-http", + "tower-http 0.6.11", "tower-service", "url", "wasm-bindgen", @@ -2711,11 +2763,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsqlite-vfs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353b74f9810d5b085b6c42c9d86a5f0fdb795553235cdd48753b32e3809d18a7" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "rusqlite" -version = "0.32.1" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "da9bba6f3506135e120a6586df05be18b48d18896600646a4bec8f993d91a594" dependencies = [ "bitflags", "fallible-iterator", @@ -2723,6 +2784,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -2915,9 +2977,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -2968,18 +3030,18 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -3080,6 +3142,22 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +[[package]] +name = "sqlcipher-src" +version = "419.0.6+sqlcipher-4.19.0-sqlite-3.53.4-libtomcrypt-1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab732d48ebf2a1449e94d071b26544f99845834c75c3c210e927ace097b49aa4" + +[[package]] +name = "sqlite-wasm-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36bd8e29bf08189a005b2df1df99823aa0861cabe9044e5f31ed54ca292ee618" +dependencies = [ + "cc", + "rsqlite-vfs", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3374,6 +3452,24 @@ name = "tower-http" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-http" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ "bitflags", "bytes", @@ -3390,10 +3486,8 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", "tower-layer", "tower-service", - "url", ] [[package]] @@ -3562,12 +3656,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common", - "subtle", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -3602,9 +3696,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.27.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -4123,9 +4217,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" diff --git a/app/Cargo.toml b/app/Cargo.toml index 16d012d..02d4a7a 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] dioxus = { version = "0.7", features = ["web"] } serde_json = "1" -gloo-net = "0.6" +gloo-net = "0.7" wasm-bindgen = "0.2" js-sys = "0.3" web-sys = { version = "0.3", features = ["Window", "Location"] } diff --git a/server/Cargo.toml b/server/Cargo.toml index cad451c..aee8bf8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,14 +11,14 @@ workspace = true [dependencies] axum = { version = "0.8", features = ["multipart"] } tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] } -rusqlite = { version = "0.32", features = ["bundled-sqlcipher-vendored-openssl"] } +rusqlite = { version = "0.41", features = ["bundled-sqlcipher-vendored-openssl"] } serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1", features = ["v4"] } chrono = "0.4" -base64 = "0.22" -sha2 = "0.10" -tower-http = { version = "0.6", features = ["fs", "set-header"] } +base64 = "0.23" +sha2 = "0.11" +tower-http = { version = "0.7", features = ["fs", "set-header"] } reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] } url = "2" async-stream = "0.3" @@ -29,11 +29,11 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } dotenvy = "0.15" fs2 = "0.4" libc = "0.2" -aes-gcm = "0.10" -hkdf = "0.12" -rand = "0.8" +aes-gcm = "0.11" +hkdf = "0.13" +rand = "0.10" zeroize = "1" -bip39 = "2.2" +bip39 = "3.0" [target.'cfg(target_os = "macos")'.dependencies] keyring = { version = "3.6.3", features = ["apple-native"] } From f6cdf884d79025530e298c889cbdc6e4cad5575e Mon Sep 17 00:00:00 2001 From: Samarth MN Date: Mon, 2 Nov 2026 22:24:09 +0530 Subject: [PATCH 2/2] Port echo-server to the new rusqlite, rand and RustCrypto majors - rusqlite 0.41: enable `fallible_uint`, which restores the checked u64/usize conversions 0.32 had; schema names are plain `&str` now. - rand 0.10: `OsRng` is now the fallible `SysRng`; `UnwrapErr` keeps the old panic-on-entropy-failure behaviour. - sha2 0.11: digests no longer implement `LowerHex`; a small `digest` module produces the same lowercase hex, pinned by FIPS 180-2 vectors. - aes-gcm 0.11: build nonces from arrays instead of `from_slice`. - cargo-deny: SQLCipher's source now ships as `sqlcipher-src`, declaring SQLite's public-domain blessing and libtomcrypt's WTFPL; allow those for that crate only. Compatibility with data users already have is pinned by fixtures sealed with the previously shipped crates: a recovery envelope and phrase, and an encrypted library file, both of which must still open. A database written by SQLCipher 4.5.7 was also checked to open under 4.19.0. Co-Authored-By: Claude Opus 5.5 --- deny.toml | 6 ++++ docs/licenses.md | 2 +- server/Cargo.toml | 2 +- server/src/digest.rs | 45 +++++++++++++++++++++++++++++ server/src/extensions/mod.rs | 2 +- server/src/extensions/pairing.rs | 4 +-- server/src/extensions/recordings.rs | 2 +- server/src/google_oauth.rs | 4 +-- server/src/library_validation.rs | 4 +-- server/src/main.rs | 1 + server/src/protected_storage.rs | 37 +++++++++++++++++++----- server/src/recovery.rs | 44 ++++++++++++++++++++++------ server/src/speech.rs | 6 +--- server/src/speech_spool.rs | 6 ++-- server/src/store/audio.rs | 4 +-- server/src/store/library_io.rs | 6 ++-- server/src/store/mod.rs | 10 ++----- server/src/store/tests.rs | 4 +-- 18 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 server/src/digest.rs diff --git a/deny.toml b/deny.toml index fe3fd8a..d76c293 100644 --- a/deny.toml +++ b/deny.toml @@ -24,6 +24,12 @@ allow = [ "CDLA-Permissive-2.0", ] confidence-threshold = 0.9 +# The SQLCipher amalgamation bundled for the encrypted library. SQLite itself is +# public domain ("blessing") and libtomcrypt is WTFPL/public domain; both are more +# permissive than MIT. Scoped to this crate so neither licence is allowed elsewhere. +exceptions = [ + { crate = "sqlcipher-src", allow = ["blessing", "WTFPL"] }, +] [bans] multiple-versions = "allow" diff --git a/docs/licenses.md b/docs/licenses.md index 0d73688..9728b62 100644 --- a/docs/licenses.md +++ b/docs/licenses.md @@ -7,7 +7,7 @@ Echo itself is released under the [MIT licence](../LICENSE). ## Dependencies -Rust dependencies are checked by `cargo deny check` (see `deny.toml`) on every CI run. Only permissive licences are allowed: MIT, Apache-2.0 (with or without the LLVM exception), BSD-2-Clause, BSD-3-Clause, ISC, Zlib, Unicode-3.0, CC0-1.0, BSL-1.0, MPL-2.0 and CDLA-Permissive-2.0. A crate offered under several licences passes if one of them is on that list. +Rust dependencies are checked by `cargo deny check` (see `deny.toml`) on every CI run. Only permissive licences are allowed: MIT, Apache-2.0 (with or without the LLVM exception), BSD-2-Clause, BSD-3-Clause, ISC, Zlib, Unicode-3.0, CC0-1.0, BSL-1.0, MPL-2.0 and CDLA-Permissive-2.0. A crate offered under several licences passes if one of them is on that list. One exception: `sqlcipher-src`, the SQLCipher source behind the encrypted library, also carries SQLite's public-domain "blessing" and libtomcrypt's WTFPL, both more permissive than MIT, so `deny.toml` allows those two for that crate only. The runtime JavaScript dependencies: diff --git a/server/Cargo.toml b/server/Cargo.toml index aee8bf8..10d9d48 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] axum = { version = "0.8", features = ["multipart"] } tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] } -rusqlite = { version = "0.41", features = ["bundled-sqlcipher-vendored-openssl"] } +rusqlite = { version = "0.41", features = ["bundled-sqlcipher-vendored-openssl", "fallible_uint"] } serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1", features = ["v4"] } diff --git a/server/src/digest.rs b/server/src/digest.rs new file mode 100644 index 0000000..8ae350f --- /dev/null +++ b/server/src/digest.rs @@ -0,0 +1,45 @@ +//! Lowercase hexadecimal SHA-256 digests: the form the library stores and verifies. +//! +//! `sha2` 0.11 digests are `hybrid_array` arrays, which do not implement `LowerHex`, +//! so `format!("{:x}", ...)` no longer works on them. + +use sha2::{Digest, Sha256}; +use std::fmt::Write as _; + +/// Encodes bytes as lowercase hex, two characters per byte. +pub(crate) fn lower_hex(bytes: &[u8]) -> String { + bytes + .iter() + .fold(String::with_capacity(bytes.len() * 2), |mut out, byte| { + // Writing to a String cannot fail. + let _ = write!(out, "{byte:02x}"); + out + }) +} + +/// The lowercase hex SHA-256 digest of `data`. +pub(crate) fn sha256_hex(data: impl AsRef<[u8]>) -> String { + lower_hex(&Sha256::digest(data.as_ref())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn matches_the_fips_180_2_test_vectors() { + assert_eq!( + sha256_hex(b"abc"), + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + ); + assert_eq!( + sha256_hex(b""), + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ); + } + + #[test] + fn pads_every_byte_to_two_digits() { + assert_eq!(lower_hex(&[0x00, 0x0f, 0xa0, 0xff]), "000fa0ff"); + } +} diff --git a/server/src/extensions/mod.rs b/server/src/extensions/mod.rs index b68964b..121e05b 100644 --- a/server/src/extensions/mod.rs +++ b/server/src/extensions/mod.rs @@ -50,7 +50,7 @@ fn seconds() -> i64 { chrono::Utc::now().timestamp() } fn hash(data: impl AsRef<[u8]>) -> String { - format!("{:x}", Sha256::digest(data.as_ref())) + crate::digest::sha256_hex(data.as_ref()) } fn uid(raw: &str) -> Result<()> { if Uuid::parse_str(raw).is_err() || raw.len() != 36 { diff --git a/server/src/extensions/pairing.rs b/server/src/extensions/pairing.rs index 924a695..0df6935 100644 --- a/server/src/extensions/pairing.rs +++ b/server/src/extensions/pairing.rs @@ -5,7 +5,7 @@ use super::{ ApiError, Connection, HeaderMap, IntoResponse, Json, OptionalExtension, Path, Response, Result, StatusCode, Uuid, Value, LIBRARY_ID, }; -use rand::{rngs::OsRng, RngCore}; +use rand::{rand_core::UnwrapErr, rngs::SysRng, Rng}; use std::sync::{Mutex, OnceLock}; /// A request stays decidable for this long; decided rows are kept as long again for claim retries. @@ -86,7 +86,7 @@ fn consume_intent(value: &str, now: i64) -> Result { /// Issue a single-use, two-minute token that pre-approves one Echo-initiated pairing. pub(super) async fn intent() -> Result> { let mut bytes = [0u8; 32]; - OsRng.fill_bytes(&mut bytes); + UnwrapErr(SysRng).fill_bytes(&mut bytes); let value = bytes .iter() .flat_map(|byte| [byte >> 4, byte & 0x0f]) diff --git a/server/src/extensions/recordings.rs b/server/src/extensions/recordings.rs index 258918e..7f439ef 100644 --- a/server/src/extensions/recordings.rs +++ b/server/src/extensions/recordings.rs @@ -183,7 +183,7 @@ fn verify_complete_files(db: &Connection, r: &Value) -> Result<()> { plaintext.fill(0); offset += count as u64; } - if format!("{:x}", digest.finalize()) != expected_digest { + if crate::digest::lower_hex(&digest.finalize()) != expected_digest { return Err(damaged()); } } diff --git a/server/src/google_oauth.rs b/server/src/google_oauth.rs index c04b0e5..f381fc6 100644 --- a/server/src/google_oauth.rs +++ b/server/src/google_oauth.rs @@ -7,7 +7,7 @@ use axum::{ response::{IntoResponse, Redirect, Response}, }; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use rand::{rngs::OsRng, RngCore}; +use rand::{rand_core::UnwrapErr, rngs::SysRng, Rng}; use sha2::{Digest, Sha256}; use std::{ collections::HashMap, @@ -74,7 +74,7 @@ pub(super) fn config() -> Result { } fn random_secret() -> Zeroizing { let mut bytes = Zeroizing::new([0; 32]); - OsRng.fill_bytes(bytes.as_mut()); + UnwrapErr(SysRng).fill_bytes(bytes.as_mut()); Zeroizing::new(URL_SAFE_NO_PAD.encode(bytes.as_ref())) } fn same_secret(a: &str, b: &str) -> bool { diff --git a/server/src/library_validation.rs b/server/src/library_validation.rs index 1f2d5ad..6b4ccc1 100644 --- a/server/src/library_validation.rs +++ b/server/src/library_validation.rs @@ -1,6 +1,5 @@ //! Authenticate an offline local library, including unindexed recovery files. use crate::{security::ApiError, store}; -use sha2::{Digest, Sha256}; use std::path::Path; fn verify_protected_tree(path: &Path, depth: usize) -> Result<(), ApiError> { @@ -94,8 +93,7 @@ pub(crate) fn verify() -> Result<(), ApiError> { } let path = store::data_dir()?.join(area).join(id).join(file); let data = zeroize::Zeroizing::new(store::read_protected(&path)?); - if data.len() as u64 != bytes || format!("{:x}", Sha256::digest(data.as_slice())) != digest - { + if data.len() as u64 != bytes || crate::digest::sha256_hex(data.as_slice()) != digest { return Err(ApiError::bad("The selected library contains missing or damaged audio. Restore a complete backup; the original has not been changed.")); } } diff --git a/server/src/main.rs b/server/src/main.rs index 2b27cc8..840cca2 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,6 +1,7 @@ #![warn(unreachable_pub)] mod api; mod config; +mod digest; mod extensions; mod integrations; mod library_schema; diff --git a/server/src/protected_storage.rs b/server/src/protected_storage.rs index 2e0cef9..bd7af24 100644 --- a/server/src/protected_storage.rs +++ b/server/src/protected_storage.rs @@ -7,7 +7,7 @@ use aes_gcm::{ }; use base64::{engine::general_purpose::STANDARD, Engine}; use hkdf::Hkdf; -use rand::{rngs::OsRng, RngCore}; +use rand::{rand_core::UnwrapErr, rngs::SysRng, Rng}; use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::{ @@ -171,7 +171,7 @@ pub(crate) fn library_key(root: &Path) -> Result { let entry = keyring::Entry::new("com.echo.voice.library", &key_id) .map_err(|e| std::io::Error::other(format!("Keychain entry: {e}")))?; let mut bytes = Zeroizing::new([0u8; 32]); - OsRng.fill_bytes(bytes.as_mut()); + UnwrapErr(SysRng).fill_bytes(bytes.as_mut()); let encoded = Zeroizing::new(STANDARD.encode(bytes.as_ref())); entry .set_password(&encoded) @@ -361,12 +361,12 @@ pub(crate) fn write_file( } let mut header = [0u8; 48]; header[..8].copy_from_slice(MAGIC); - OsRng.fill_bytes(&mut header[8..40]); + UnwrapErr(SysRng).fill_bytes(&mut header[8..40]); header[40..].copy_from_slice(&(plaintext.len() as u64).to_le_bytes()); let cipher = key.cipher(&header[8..40], context)?; let tag = cipher .encrypt( - Nonce::from_slice(&nonce(0)), + &Nonce::from(nonce(0)), Payload { msg: &[], aad: &header, @@ -380,7 +380,7 @@ pub(crate) fn write_file( let sequence = index as u64 + 1; let sealed = cipher .encrypt( - Nonce::from_slice(&nonce(sequence)), + &Nonce::from(nonce(sequence)), Payload { msg: block, aad: &binding(&header, sequence), @@ -419,7 +419,7 @@ impl Reader { file.read_exact(&mut tag)?; cipher .decrypt( - Nonce::from_slice(&nonce(0)), + &Nonce::from(nonce(0)), Payload { msg: &tag, aad: &header, @@ -456,7 +456,7 @@ impl Reader { let plaintext = Zeroizing::new( self.cipher .decrypt( - Nonce::from_slice(&nonce(index + 1)), + &Nonce::from(nonce(index + 1)), Payload { msg: &sealed, aad: &binding(&self.header, index + 1), @@ -557,6 +557,29 @@ mod tests { fs::remove_file(active).unwrap(); } } + /// Written by the crates this library shipped with (hkdf 0.12, aes-gcm 0.10). + /// Encrypted files already on users' disks must keep opening after upgrades. + const SHIPPED_FILE: &str = "RUNIT0VOQzHSchOcIAW11v+NueCHpGmnMm29t8k2z+FrAVflw+4AAigAAAAAAAAAKM54pF2rdmSj7g3URH63j+Fz2p2FVOqUCYlRhrwSEIV7XNhCJ/5hpztO57cgcImXmvaeYQyzDbsEQaW0Qx2Ac0Yv7+WT+qCW"; + + #[test] + fn files_written_by_shipped_crates_still_open() { + use base64::{engine::general_purpose::STANDARD, Engine as _}; + let dir = fixture(); + let path = dir.path().join("shipped"); + fs::write(&path, STANDARD.decode(SHIPPED_FILE).unwrap()).unwrap(); + let key = LibraryKey::from_bytes([7; 32]); + let data: Vec = (0u8..40).collect(); + let mut reader = Reader::open(&key, &path, "audio:fixture:part-1").unwrap(); + assert_eq!(reader.read_all().unwrap(), data); + assert!(Reader::open(&key, &path, "audio:fixture:part-2").is_err()); + assert!(Reader::open( + &LibraryKey::from_bytes([8; 32]), + &path, + "audio:fixture:part-1" + ) + .is_err()); + } + #[test] fn ranges_and_empty_files_are_authenticated() { let dir = fixture(); diff --git a/server/src/recovery.rs b/server/src/recovery.rs index 64cb66c..18a1b65 100644 --- a/server/src/recovery.rs +++ b/server/src/recovery.rs @@ -13,7 +13,7 @@ use axum::{ use base64::{engine::general_purpose::STANDARD, Engine}; use bip39::{Language, Mnemonic}; use hkdf::Hkdf; -use rand::{rngs::OsRng, RngCore}; +use rand::{rand_core::UnwrapErr, rngs::SysRng, Rng}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use sha2::{Digest, Sha256}; @@ -69,10 +69,10 @@ fn key_for(secret: &[u8], salt: &str, purpose: &str) -> Result { } fn seal(cipher: &Aes256Gcm, data: &[u8], context: &[u8]) -> Result { let mut nonce = [0u8; 12]; - OsRng.fill_bytes(&mut nonce); + UnwrapErr(SysRng).fill_bytes(&mut nonce); let ciphertext = cipher .encrypt( - Nonce::from_slice(&nonce), + &Nonce::from(nonce), Payload { msg: data, aad: context, @@ -93,10 +93,11 @@ fn open( if sealed.nonce.len() != 16 || sealed.ciphertext.len() > ((max + 16).div_ceil(3) * 4) { return Err(failure()); } - let nonce = STANDARD.decode(&sealed.nonce).map_err(|_| failure())?; - if nonce.len() != 12 { - return Err(failure()); - } + let nonce: [u8; 12] = STANDARD + .decode(&sealed.nonce) + .map_err(|_| failure())? + .try_into() + .map_err(|_| failure())?; let ciphertext = STANDARD.decode(&sealed.ciphertext).map_err(|_| failure())?; if ciphertext.len() > max + 16 { return Err(failure()); @@ -104,7 +105,7 @@ fn open( Ok(Zeroizing::new( cipher .decrypt( - Nonce::from_slice(&nonce), + &Nonce::from(nonce), Payload { msg: &ciphertext, aad: context, @@ -173,7 +174,7 @@ fn validate_recovery(recovery: &Recovery, master: &[u8; 32]) -> Result<()> { } fn make_recovery(master: &[u8; 32]) -> Result<(Recovery, Zeroizing)> { let mut entropy = Zeroizing::new([0u8; 32]); - OsRng.fill_bytes(entropy.as_mut()); + UnwrapErr(SysRng).fill_bytes(entropy.as_mut()); let phrase = Zeroizing::new( Mnemonic::from_entropy(entropy.as_ref()) .map_err(|_| failure())? @@ -511,6 +512,31 @@ fn decode_backup(backup: &Backup, key: &[u8; 32]) -> Result { #[cfg(test)] mod tests { use super::*; + + /// Sealed by the crates this library shipped with (bip39 2.2, hkdf 0.12, + /// aes-gcm 0.10, sha2 0.10). Libraries and recovery phrases already saved by + /// users must keep opening after dependency upgrades. Synthetic test vector: + /// the phrase protects the fixed test key `[42; 32]`, not a real library. + const SHIPPED_RECOVERY: &str = r#"{"version":1,"id":"dc25e00a-2ced-4996-92c1-9a5d57ceec24","fingerprint":"FZyjxA4b74aKKeOcrJveZwpsXQewXWQVGfLZumKJJQI=","sealed":{"nonce":"8HlI59EOoZ61ieat","ciphertext":"FGhI+BnDZhCHBGw3mobNhW++G2MAd29YmZB/ujr3mz9M5mJoJY4VfzC4TKkQnKFP"},"integrity":{"nonce":"GPXxI/SzTyj2pSLm","ciphertext":"up5VIm/QWcvD3eA4hc38Cg=="}}"#; + const SHIPPED_PHRASE: &str = "salute arrive door age nominee danger follow real same share blossom general trip wrap cradle bird baby demand coil zero settle erase avoid shallow"; + + #[test] + fn recovery_envelopes_sealed_by_shipped_crates_still_open() { + let recovery: Recovery = serde_json::from_str(SHIPPED_RECOVERY).unwrap(); + assert_eq!(*recover_key(&recovery, SHIPPED_PHRASE).unwrap(), [42; 32]); + validate_recovery(&recovery, &[42; 32]).unwrap(); + let mut wrong = SHIPPED_PHRASE.split(' ').collect::>(); + wrong.swap(0, 1); + assert!(recover_key(&recovery, &wrong.join(" ")).is_err()); + } + + #[test] + fn recovery_phrases_match_the_bip39_english_test_vector() { + let phrase = Mnemonic::from_entropy(&[0; 32]).unwrap().to_string(); + assert_eq!(phrase, format!("{}art", "abandon ".repeat(23))); + let parsed = Mnemonic::parse_in(Language::English, &phrase).unwrap(); + assert_eq!(parsed.to_entropy(), vec![0; 32]); + } #[test] fn library_recovery_ignores_legacy_chatgpt_files() { let _guard = store::TEST_LIBRARY_LOCK.blocking_lock(); diff --git a/server/src/speech.rs b/server/src/speech.rs index 120c771..32595e6 100644 --- a/server/src/speech.rs +++ b/server/src/speech.rs @@ -13,7 +13,6 @@ use axum::{ use futures_util::StreamExt; use serde::Deserialize; use serde_json::{json, Value}; -use sha2::{Digest, Sha256}; use std::{ collections::{HashMap, VecDeque}, path::{Component, Path as FsPath, PathBuf}, @@ -354,10 +353,7 @@ impl Drop for UploadGuard { /// Name transient audio by canonical library identity, never by a shared machine temp path. fn spool_namespace(project: &FsPath, data: &FsPath) -> Result { let canonical = std::fs::canonicalize(data)?; - let identity = format!( - "{:x}", - Sha256::digest(canonical.as_os_str().as_encoded_bytes()) - ); + let identity = crate::digest::sha256_hex(canonical.as_os_str().as_encoded_bytes()); let temporary = project.join("tmp"); let namespace = temporary.join(format!("native-speech-{identity}")); for folder in [&temporary, &namespace] { diff --git a/server/src/speech_spool.rs b/server/src/speech_spool.rs index 33be324..d75f4b8 100644 --- a/server/src/speech_spool.rs +++ b/server/src/speech_spool.rs @@ -4,7 +4,7 @@ use aes_gcm::{ aead::{Aead, KeyInit, Payload}, Aes256Gcm, Nonce, }; -use rand::{rngs::OsRng, RngCore}; +use rand::{rand_core::UnwrapErr, rngs::SysRng, Rng}; use std::path::Path; use tokio::io::AsyncWriteExt; use zeroize::{Zeroize, Zeroizing}; @@ -22,7 +22,7 @@ pub(super) struct Writer { impl Writer { pub(super) async fn create(path: &Path) -> std::io::Result { let mut key = Zeroizing::new([0; 32]); - OsRng.fill_bytes(key.as_mut()); + UnwrapErr(SysRng).fill_bytes(key.as_mut()); let cipher = Aes256Gcm::new_from_slice(key.as_ref()) .map_err(|_| std::io::Error::other("Invalid speech spool key."))?; let mut options = tokio::fs::OpenOptions::new(); @@ -68,7 +68,7 @@ impl Writer { let encrypted = self .cipher .encrypt( - Nonce::from_slice(&nonce), + &Nonce::from(nonce), Payload { msg: block, aad: &binding, diff --git a/server/src/store/audio.rs b/server/src/store/audio.rs index 8b711f7..15a963e 100644 --- a/server/src/store/audio.rs +++ b/server/src/store/audio.rs @@ -81,7 +81,7 @@ pub(crate) fn add_audio( if sequence.is_some_and(|s| !(0..=1_000_000).contains(&s)) { return Err(ApiError::bad("Invalid audio sequence.")); } - let digest = format!("{:x}", Sha256::digest(data)); + let digest = crate::digest::sha256_hex(data); let mut written: Option = None; let result = with_db(|db| { let tx = db.transaction()?; @@ -120,7 +120,7 @@ pub(crate) fn add_audio( } let path = data_dir()?.join("audio").join(key).join(file); let saved = read_protected(&path)?; - if saved.len() as u64 != bytes || format!("{:x}", Sha256::digest(&saved)) != hash { + if saved.len() as u64 != bytes || crate::digest::sha256_hex(&saved) != hash { return Err(ApiError::new( 410, "The saved audio chunk is damaged. Restore the recording from a backup.", diff --git a/server/src/store/library_io.rs b/server/src/store/library_io.rs index 431092e..3d8f69e 100644 --- a/server/src/store/library_io.rs +++ b/server/src/store/library_io.rs @@ -3,7 +3,7 @@ use super::{ list_db, list_meetings, migrate_legacy_notes_settings, mkdir, now, object, params, read_protected, settings_db, string, timestamp, unique, valid_id, validate_meeting, validate_settings, validate_vocabulary, vocabulary_db, with_db, write_protected_for, ApiError, - Digest, Engine, HashMap, Path, PathBuf, Result, Sha256, Value, MAX_AUDIO, MAX_BACKUP, STANDARD, + Engine, HashMap, Path, PathBuf, Result, Value, MAX_AUDIO, MAX_BACKUP, STANDARD, }; use std::fmt::Write as _; @@ -68,7 +68,7 @@ pub(crate) fn export_library() -> Result { return Err(ApiError::new(500, "Audio links cannot be exported.")); } let data = read_protected(&path)?; - if data.len() as u64 != bytes || format!("{:x}", Sha256::digest(&data)) != digest { + if data.len() as u64 != bytes || crate::digest::sha256_hex(&data) != digest { return Err(ApiError::new( 410, "An audio file is incomplete. Restore it before creating a backup.", @@ -337,7 +337,7 @@ pub(crate) fn import_library(input: &Value) -> Result { chunk.filename, i64::try_from(chunk.data.len()) .map_err(|_| ApiError::bad("Audio chunk is too large."))?, - format!("{:x}", Sha256::digest(&chunk.data)) + crate::digest::sha256_hex(&chunk.data) ], )?; } diff --git a/server/src/store/mod.rs b/server/src/store/mod.rs index 87ff15d..118a82a 100644 --- a/server/src/store/mod.rs +++ b/server/src/store/mod.rs @@ -6,7 +6,6 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use chrono::Utc; use rusqlite::{params, Connection, OptionalExtension}; use serde_json::{json, Value}; -use sha2::{Digest, Sha256}; use std::{ collections::{HashMap, HashSet}, fs, @@ -268,11 +267,7 @@ fn open_protected_database(root: &Path, key: &LibraryKey) -> Result source.execute_batch("PRAGMA encrypted.cipher_compatibility=4;")?; source.query_row("SELECT sqlcipher_export('encrypted')", [], |_| Ok(()))?; let schema: i64 = source.pragma_query_value(None, "user_version", |row| row.get(0))?; - source.pragma_update( - Some(rusqlite::DatabaseName::Attached("encrypted")), - "user_version", - schema, - )?; + source.pragma_update(Some("encrypted"), "user_version", schema)?; source.execute_batch("DETACH DATABASE encrypted;")?; let verified = unlock_database(&staged, key)?; let check: String = verified.query_row("PRAGMA integrity_check", [], |r| r.get(0))?; @@ -570,8 +565,7 @@ fn migrate_file(path: &Path, expected: Option<(u64, &str)>) -> Result<()> { fs::read(path)? }; if let Some((bytes, digest)) = expected { - if plaintext.len() as u64 != bytes || format!("{:x}", Sha256::digest(&plaintext)) != digest - { + if plaintext.len() as u64 != bytes || crate::digest::sha256_hex(&plaintext) != digest { return Err(ApiError::new( 410, "A legacy audio file is damaged. Migration paused without replacing it.", diff --git a/server/src/store/tests.rs b/server/src/store/tests.rs index 117912a..b985be7 100644 --- a/server/src/store/tests.rs +++ b/server/src/store/tests.rs @@ -97,7 +97,7 @@ fn database_bytes(root: &Path) -> Vec<(String, String)> { .filter_map(|name| { fs::read(root.join(name)) .ok() - .map(|bytes| (name.into(), format!("{:x}", Sha256::digest(bytes)))) + .map(|bytes| (name.into(), crate::digest::sha256_hex(bytes))) }) .collect() } @@ -183,7 +183,7 @@ fn legacy_database_and_files_migrate_resumably_and_wrong_keys_fail_closed() { sequence, format!("legacy-{sequence}.chunk"), audio.len(), - format!("{:x}", Sha256::digest(audio)) + crate::digest::sha256_hex(audio) ], ) .unwrap();