max / balanced_breakfast
3 files changed,
+112 insertions,
-63 deletions
| @@ -10,12 +10,12 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" | |||
| 10 | 10 | ||
| 11 | 11 | [[package]] | |
| 12 | 12 | name = "aead" | |
| 13 | - | version = "0.5.2" | |
| 13 | + | version = "0.6.1" | |
| 14 | 14 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 15 | - | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" | |
| 15 | + | checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" | |
| 16 | 16 | dependencies = [ | |
| 17 | - | "crypto-common 0.1.7", | |
| 18 | - | "generic-array", | |
| 17 | + | "crypto-common 0.2.2", | |
| 18 | + | "inout 0.2.2", | |
| 19 | 19 | ] | |
| 20 | 20 | ||
| 21 | 21 | [[package]] | |
| @@ -25,19 +25,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 25 | 25 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" | |
| 26 | 26 | dependencies = [ | |
| 27 | 27 | "cfg-if", | |
| 28 | - | "cipher", | |
| 28 | + | "cipher 0.4.4", | |
| 29 | 29 | "cpufeatures 0.2.17", | |
| 30 | 30 | ] | |
| 31 | 31 | ||
| 32 | 32 | [[package]] | |
| 33 | + | name = "aes" | |
| 34 | + | version = "0.9.1" | |
| 35 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 36 | + | checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" | |
| 37 | + | dependencies = [ | |
| 38 | + | "cipher 0.5.2", | |
| 39 | + | "cpubits", | |
| 40 | + | "cpufeatures 0.3.0", | |
| 41 | + | ] | |
| 42 | + | ||
| 43 | + | [[package]] | |
| 33 | 44 | name = "aes-gcm" | |
| 34 | - | version = "0.10.3" | |
| 45 | + | version = "0.11.0" | |
| 35 | 46 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 36 | - | checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" | |
| 47 | + | checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" | |
| 37 | 48 | dependencies = [ | |
| 38 | 49 | "aead", | |
| 39 | - | "aes", | |
| 40 | - | "cipher", | |
| 50 | + | "aes 0.9.1", | |
| 51 | + | "cipher 0.5.2", | |
| 41 | 52 | "ctr", | |
| 42 | 53 | "ghash", | |
| 43 | 54 | "subtle", | |
| @@ -675,7 +686,7 @@ version = "0.1.2" | |||
| 675 | 686 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 676 | 687 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" | |
| 677 | 688 | dependencies = [ | |
| 678 | - | "cipher", | |
| 689 | + | "cipher 0.4.4", | |
| 679 | 690 | ] | |
| 680 | 691 | ||
| 681 | 692 | [[package]] | |
| @@ -723,37 +734,26 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |||
| 723 | 734 | ||
| 724 | 735 | [[package]] | |
| 725 | 736 | name = "chacha20" | |
| 726 | - | version = "0.9.1" | |
| 727 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 728 | - | checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" | |
| 729 | - | dependencies = [ | |
| 730 | - | "cfg-if", | |
| 731 | - | "cipher", | |
| 732 | - | "cpufeatures 0.2.17", | |
| 733 | - | ] | |
| 734 | - | ||
| 735 | - | [[package]] | |
| 736 | - | name = "chacha20" | |
| 737 | 737 | version = "0.10.1" | |
| 738 | 738 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 739 | 739 | checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" | |
| 740 | 740 | dependencies = [ | |
| 741 | 741 | "cfg-if", | |
| 742 | + | "cipher 0.5.2", | |
| 742 | 743 | "cpufeatures 0.3.0", | |
| 743 | 744 | "rand_core 0.10.1", | |
| 744 | 745 | ] | |
| 745 | 746 | ||
| 746 | 747 | [[package]] | |
| 747 | 748 | name = "chacha20poly1305" | |
| 748 | - | version = "0.10.1" | |
| 749 | + | version = "0.11.0" | |
| 749 | 750 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 750 | - | checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" | |
| 751 | + | checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" | |
| 751 | 752 | dependencies = [ | |
| 752 | 753 | "aead", | |
| 753 | - | "chacha20 0.9.1", | |
| 754 | - | "cipher", | |
| 754 | + | "chacha20", | |
| 755 | + | "cipher 0.5.2", | |
| 755 | 756 | "poly1305", | |
| 756 | - | "zeroize", | |
| 757 | 757 | ] | |
| 758 | 758 | ||
| 759 | 759 | [[package]] | |
| @@ -777,8 +777,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 777 | 777 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" | |
| 778 | 778 | dependencies = [ | |
| 779 | 779 | "crypto-common 0.1.7", | |
| 780 | - | "inout", | |
| 781 | - | "zeroize", | |
| 780 | + | "inout 0.1.4", | |
| 781 | + | ] | |
| 782 | + | ||
| 783 | + | [[package]] | |
| 784 | + | name = "cipher" | |
| 785 | + | version = "0.5.2" | |
| 786 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 787 | + | checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" | |
| 788 | + | dependencies = [ | |
| 789 | + | "block-buffer 0.12.1", | |
| 790 | + | "crypto-common 0.2.2", | |
| 791 | + | "inout 0.2.2", | |
| 782 | 792 | ] | |
| 783 | 793 | ||
| 784 | 794 | [[package]] | |
| @@ -899,6 +909,12 @@ dependencies = [ | |||
| 899 | 909 | ] | |
| 900 | 910 | ||
| 901 | 911 | [[package]] | |
| 912 | + | name = "cpubits" | |
| 913 | + | version = "0.1.1" | |
| 914 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 915 | + | checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" | |
| 916 | + | ||
| 917 | + | [[package]] | |
| 902 | 918 | name = "cpufeatures" | |
| 903 | 919 | version = "0.2.17" | |
| 904 | 920 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -977,7 +993,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 977 | 993 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" | |
| 978 | 994 | dependencies = [ | |
| 979 | 995 | "generic-array", | |
| 980 | - | "rand_core 0.6.4", | |
| 981 | 996 | "typenum", | |
| 982 | 997 | ] | |
| 983 | 998 | ||
| @@ -987,7 +1002,9 @@ version = "0.2.2" | |||
| 987 | 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 988 | 1003 | checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" | |
| 989 | 1004 | dependencies = [ | |
| 1005 | + | "getrandom 0.4.3", | |
| 990 | 1006 | "hybrid-array", | |
| 1007 | + | "rand_core 0.10.1", | |
| 991 | 1008 | ] | |
| 992 | 1009 | ||
| 993 | 1010 | [[package]] | |
| @@ -1095,11 +1112,11 @@ dependencies = [ | |||
| 1095 | 1112 | ||
| 1096 | 1113 | [[package]] | |
| 1097 | 1114 | name = "ctr" | |
| 1098 | - | version = "0.9.2" | |
| 1115 | + | version = "0.10.1" | |
| 1099 | 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1100 | - | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" | |
| 1117 | + | checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" | |
| 1101 | 1118 | dependencies = [ | |
| 1102 | - | "cipher", | |
| 1119 | + | "cipher 0.5.2", | |
| 1103 | 1120 | ] | |
| 1104 | 1121 | ||
| 1105 | 1122 | [[package]] | |
| @@ -1941,11 +1958,10 @@ dependencies = [ | |||
| 1941 | 1958 | ||
| 1942 | 1959 | [[package]] | |
| 1943 | 1960 | name = "ghash" | |
| 1944 | - | version = "0.5.1" | |
| 1961 | + | version = "0.6.0" | |
| 1945 | 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1946 | - | checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" | |
| 1963 | + | checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" | |
| 1947 | 1964 | dependencies = [ | |
| 1948 | - | "opaque-debug", | |
| 1949 | 1965 | "polyval", | |
| 1950 | 1966 | ] | |
| 1951 | 1967 | ||
| @@ -2597,6 +2613,15 @@ dependencies = [ | |||
| 2597 | 2613 | ] | |
| 2598 | 2614 | ||
| 2599 | 2615 | [[package]] | |
| 2616 | + | name = "inout" | |
| 2617 | + | version = "0.2.2" | |
| 2618 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2619 | + | checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" | |
| 2620 | + | dependencies = [ | |
| 2621 | + | "hybrid-array", | |
| 2622 | + | ] | |
| 2623 | + | ||
| 2624 | + | [[package]] | |
| 2600 | 2625 | name = "ipnet" | |
| 2601 | 2626 | version = "2.11.0" | |
| 2602 | 2627 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -3524,12 +3549,6 @@ dependencies = [ | |||
| 3524 | 3549 | ] | |
| 3525 | 3550 | ||
| 3526 | 3551 | [[package]] | |
| 3527 | - | name = "opaque-debug" | |
| 3528 | - | version = "0.3.1" | |
| 3529 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3530 | - | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" | |
| 3531 | - | ||
| 3532 | - | [[package]] | |
| 3533 | 3552 | name = "open" | |
| 3534 | 3553 | version = "5.3.3" | |
| 3535 | 3554 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -4001,24 +4020,22 @@ dependencies = [ | |||
| 4001 | 4020 | ||
| 4002 | 4021 | [[package]] | |
| 4003 | 4022 | name = "poly1305" | |
| 4004 | - | version = "0.8.0" | |
| 4023 | + | version = "0.9.1" | |
| 4005 | 4024 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4006 | - | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" | |
| 4025 | + | checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" | |
| 4007 | 4026 | dependencies = [ | |
| 4008 | - | "cpufeatures 0.2.17", | |
| 4009 | - | "opaque-debug", | |
| 4027 | + | "cpufeatures 0.3.0", | |
| 4010 | 4028 | "universal-hash", | |
| 4011 | 4029 | ] | |
| 4012 | 4030 | ||
| 4013 | 4031 | [[package]] | |
| 4014 | 4032 | name = "polyval" | |
| 4015 | - | version = "0.6.2" | |
| 4033 | + | version = "0.7.3" | |
| 4016 | 4034 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4017 | - | checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" | |
| 4035 | + | checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" | |
| 4018 | 4036 | dependencies = [ | |
| 4019 | - | "cfg-if", | |
| 4020 | - | "cpufeatures 0.2.17", | |
| 4021 | - | "opaque-debug", | |
| 4037 | + | "cpubits", | |
| 4038 | + | "cpufeatures 0.3.0", | |
| 4022 | 4039 | "universal-hash", | |
| 4023 | 4040 | ] | |
| 4024 | 4041 | ||
| @@ -4243,7 +4260,7 @@ version = "0.10.2" | |||
| 4243 | 4260 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4244 | 4261 | checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" | |
| 4245 | 4262 | dependencies = [ | |
| 4246 | - | "chacha20 0.10.1", | |
| 4263 | + | "chacha20", | |
| 4247 | 4264 | "getrandom 0.4.3", | |
| 4248 | 4265 | "rand_core 0.10.1", | |
| 4249 | 4266 | ] | |
| @@ -4781,7 +4798,7 @@ version = "5.1.0" | |||
| 4781 | 4798 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4782 | 4799 | checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14" | |
| 4783 | 4800 | dependencies = [ | |
| 4784 | - | "aes", | |
| 4801 | + | "aes 0.8.4", | |
| 4785 | 4802 | "cbc", | |
| 4786 | 4803 | "futures-util", | |
| 4787 | 4804 | "generic-array", | |
| @@ -6741,12 +6758,12 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" | |||
| 6741 | 6758 | ||
| 6742 | 6759 | [[package]] | |
| 6743 | 6760 | name = "universal-hash" | |
| 6744 | - | version = "0.5.1" | |
| 6761 | + | version = "0.6.1" | |
| 6745 | 6762 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 6746 | - | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" | |
| 6763 | + | checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" | |
| 6747 | 6764 | dependencies = [ | |
| 6748 | - | "crypto-common 0.1.7", | |
| 6749 | - | "subtle", | |
| 6765 | + | "crypto-common 0.2.2", | |
| 6766 | + | "ctutils", | |
| 6750 | 6767 | ] | |
| 6751 | 6768 | ||
| 6752 | 6769 | [[package]] |
| @@ -32,7 +32,7 @@ sqlx = { version = "0.9", features = ["runtime-tokio", "sqlite", "chrono", "uuid | |||
| 32 | 32 | roxmltree = "0.21" | |
| 33 | 33 | ||
| 34 | 34 | # Encryption | |
| 35 | - | aes-gcm = "0.10" | |
| 35 | + | aes-gcm = "0.11" | |
| 36 | 36 | base64 = "0.22" | |
| 37 | 37 | rand = "0.10" | |
| 38 | 38 |
| @@ -7,8 +7,8 @@ | |||
| 7 | 7 | //! If absent, returns the value as-is (plaintext passthrough). This allows existing | |
| 8 | 8 | //! configs to work without migration. | |
| 9 | 9 | ||
| 10 | - | use aes_gcm::aead::{Aead, OsRng}; | |
| 11 | - | use aes_gcm::{AeadCore, Aes256Gcm, KeyInit}; | |
| 10 | + | use aes_gcm::aead::Aead; | |
| 11 | + | use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; | |
| 12 | 12 | use base64::Engine; | |
| 13 | 13 | use base64::engine::general_purpose::STANDARD as BASE64; | |
| 14 | 14 | use bb_interface::{ConfigFieldType, ConfigSchema}; | |
| @@ -160,14 +160,18 @@ pub fn load_or_create_key_from_keychain(file_path: &Path) -> Result<EncryptionKe | |||
| 160 | 160 | /// Encrypt a plaintext string field using AES-256-GCM | |
| 161 | 161 | pub fn encrypt_field(plaintext: &str, key: &EncryptionKey) -> Result<String, String> { | |
| 162 | 162 | let cipher = Aes256Gcm::new((&**key).into()); | |
| 163 | - | let nonce = Aes256Gcm::generate_nonce(&mut OsRng); | |
| 163 | + | // aes-gcm 0.11 dropped AeadCore::generate_nonce and aead's OsRng re-export, | |
| 164 | + | // so the nonce is drawn from the crate's own CSPRNG. | |
| 165 | + | let mut nonce_bytes = [0u8; 12]; | |
| 166 | + | rand::rng().fill_bytes(&mut nonce_bytes); | |
| 167 | + | let nonce = Nonce::from(nonce_bytes); | |
| 164 | 168 | let ciphertext = cipher | |
| 165 | 169 | .encrypt(&nonce, plaintext.as_bytes()) | |
| 166 | 170 | .map_err(|e| format!("Encryption failed: {e}"))?; | |
| 167 | 171 | ||
| 168 | 172 | // nonce (12 bytes) || ciphertext+tag | |
| 169 | 173 | let mut payload = Vec::with_capacity(12 + ciphertext.len()); | |
| 170 | - | payload.extend_from_slice(&nonce); | |
| 174 | + | payload.extend_from_slice(&nonce_bytes); | |
| 171 | 175 | payload.extend_from_slice(&ciphertext); | |
| 172 | 176 | ||
| 173 | 177 | Ok(format!("{}{}", PREFIX, BASE64.encode(&payload))) | |
| @@ -189,11 +193,12 @@ pub fn decrypt_field(value: &str, key: &EncryptionKey) -> Result<String, String> | |||
| 189 | 193 | } | |
| 190 | 194 | ||
| 191 | 195 | let (nonce_bytes, ciphertext) = payload.split_at(12); | |
| 192 | - | let nonce = aes_gcm::Nonce::from_slice(nonce_bytes); | |
| 196 | + | let nonce = Nonce::try_from(nonce_bytes) | |
| 197 | + | .map_err(|e| format!("Encrypted payload has a malformed nonce: {e}"))?; | |
| 193 | 198 | let cipher = Aes256Gcm::new((&**key).into()); | |
| 194 | 199 | ||
| 195 | 200 | let plaintext = cipher | |
| 196 | - | .decrypt(nonce, ciphertext) | |
| 201 | + | .decrypt(&nonce, ciphertext) | |
| 197 | 202 | .map_err(|e| format!("Decryption failed: {e}"))?; | |
| 198 | 203 | ||
| 199 | 204 | String::from_utf8(plaintext).map_err(|e| format!("Decrypted data is not valid UTF-8: {e}")) | |
| @@ -273,6 +278,33 @@ mod tests { | |||
| 273 | 278 | use super::*; | |
| 274 | 279 | use bb_interface::ConfigField; | |
| 275 | 280 | ||
| 281 | + | /// Known-answer vector for the at-rest field envelope, computed outside | |
| 282 | + | /// RustCrypto (python `cryptography`'s AES-GCM). Every encrypted config | |
| 283 | + | /// secret already on a user's disk is sealed this way, so the envelope must | |
| 284 | + | /// keep decrypting byte-for-byte across cipher upgrades. The roundtrip test | |
| 285 | + | /// below re-encrypts with the same code and would pass even if the envelope | |
| 286 | + | /// changed shape. | |
| 287 | + | #[test] | |
| 288 | + | fn field_matches_independent_known_answer() { | |
| 289 | + | const STORED: &str = | |
| 290 | + | "bb_enc:v1:ICEiIyQlJicoKSorsFiGEwP2fGd9XDGromqRjfA/jfDy5VrIuJCkEeUh+ps/4a3i44U="; | |
| 291 | + | let mut raw = [0u8; 32]; | |
| 292 | + | for (i, b) in raw.iter_mut().enumerate() { | |
| 293 | + | *b = u8::try_from(i).unwrap(); | |
| 294 | + | } | |
| 295 | + | let key: EncryptionKey = Zeroizing::new(raw); | |
| 296 | + | ||
| 297 | + | assert_eq!( | |
| 298 | + | decrypt_field(STORED, &key).unwrap(), | |
| 299 | + | "bb config secret value" | |
| 300 | + | ); | |
| 301 | + | ||
| 302 | + | // A different key must fail, so the vector shows the field is actually | |
| 303 | + | // bound to the stored key rather than merely decodable. | |
| 304 | + | let other: EncryptionKey = Zeroizing::new([9u8; 32]); | |
| 305 | + | assert!(decrypt_field(STORED, &other).is_err()); | |
| 306 | + | } | |
| 307 | + | ||
| 276 | 308 | #[test] | |
| 277 | 309 | fn roundtrip_encrypt_decrypt() { | |
| 278 | 310 | let key = generate_key(); |