max / makenotwork
3 files changed,
+100 insertions,
-52 deletions
| @@ -4,12 +4,12 @@ version = 4 | |||
| 4 | 4 | ||
| 5 | 5 | [[package]] | |
| 6 | 6 | name = "aead" | |
| 7 | - | version = "0.5.2" | |
| 7 | + | version = "0.6.1" | |
| 8 | 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | - | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" | |
| 9 | + | checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" | |
| 10 | 10 | dependencies = [ | |
| 11 | - | "crypto-common 0.1.7", | |
| 12 | - | "generic-array", | |
| 11 | + | "crypto-common 0.2.2", | |
| 12 | + | "inout 0.2.2", | |
| 13 | 13 | ] | |
| 14 | 14 | ||
| 15 | 15 | [[package]] | |
| @@ -19,7 +19,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 19 | 19 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" | |
| 20 | 20 | dependencies = [ | |
| 21 | 21 | "cfg-if", | |
| 22 | - | "cipher", | |
| 22 | + | "cipher 0.4.4", | |
| 23 | 23 | "cpufeatures 0.2.17", | |
| 24 | 24 | ] | |
| 25 | 25 | ||
| @@ -314,7 +314,7 @@ version = "0.1.2" | |||
| 314 | 314 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 315 | 315 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" | |
| 316 | 316 | dependencies = [ | |
| 317 | - | "cipher", | |
| 317 | + | "cipher 0.4.4", | |
| 318 | 318 | ] | |
| 319 | 319 | ||
| 320 | 320 | [[package]] | |
| @@ -335,37 +335,26 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |||
| 335 | 335 | ||
| 336 | 336 | [[package]] | |
| 337 | 337 | name = "chacha20" | |
| 338 | - | version = "0.9.1" | |
| 339 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 340 | - | checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" | |
| 341 | - | dependencies = [ | |
| 342 | - | "cfg-if", | |
| 343 | - | "cipher", | |
| 344 | - | "cpufeatures 0.2.17", | |
| 345 | - | ] | |
| 346 | - | ||
| 347 | - | [[package]] | |
| 348 | - | name = "chacha20" | |
| 349 | 338 | version = "0.10.1" | |
| 350 | 339 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 351 | 340 | checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" | |
| 352 | 341 | dependencies = [ | |
| 353 | 342 | "cfg-if", | |
| 343 | + | "cipher 0.5.2", | |
| 354 | 344 | "cpufeatures 0.3.0", | |
| 355 | 345 | "rand_core 0.10.1", | |
| 356 | 346 | ] | |
| 357 | 347 | ||
| 358 | 348 | [[package]] | |
| 359 | 349 | name = "chacha20poly1305" | |
| 360 | - | version = "0.10.1" | |
| 350 | + | version = "0.11.0" | |
| 361 | 351 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 362 | - | checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" | |
| 352 | + | checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" | |
| 363 | 353 | dependencies = [ | |
| 364 | 354 | "aead", | |
| 365 | - | "chacha20 0.9.1", | |
| 366 | - | "cipher", | |
| 355 | + | "chacha20", | |
| 356 | + | "cipher 0.5.2", | |
| 367 | 357 | "poly1305", | |
| 368 | - | "zeroize", | |
| 369 | 358 | ] | |
| 370 | 359 | ||
| 371 | 360 | [[package]] | |
| @@ -389,11 +378,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 389 | 378 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" | |
| 390 | 379 | dependencies = [ | |
| 391 | 380 | "crypto-common 0.1.7", | |
| 392 | - | "inout", | |
| 393 | - | "zeroize", | |
| 381 | + | "inout 0.1.4", | |
| 382 | + | ] | |
| 383 | + | ||
| 384 | + | [[package]] | |
| 385 | + | name = "cipher" | |
| 386 | + | version = "0.5.2" | |
| 387 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 388 | + | checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" | |
| 389 | + | dependencies = [ | |
| 390 | + | "block-buffer 0.12.1", | |
| 391 | + | "crypto-common 0.2.2", | |
| 392 | + | "inout 0.2.2", | |
| 394 | 393 | ] | |
| 395 | 394 | ||
| 396 | 395 | [[package]] | |
| 396 | + | name = "cmov" | |
| 397 | + | version = "0.5.4" | |
| 398 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 399 | + | checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" | |
| 400 | + | ||
| 401 | + | [[package]] | |
| 397 | 402 | name = "concurrent-queue" | |
| 398 | 403 | version = "2.5.0" | |
| 399 | 404 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -465,7 +470,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 465 | 470 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" | |
| 466 | 471 | dependencies = [ | |
| 467 | 472 | "generic-array", | |
| 468 | - | "rand_core 0.6.4", | |
| 469 | 473 | "typenum", | |
| 470 | 474 | ] | |
| 471 | 475 | ||
| @@ -475,7 +479,18 @@ version = "0.2.2" | |||
| 475 | 479 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 476 | 480 | checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" | |
| 477 | 481 | dependencies = [ | |
| 482 | + | "getrandom 0.4.1", | |
| 478 | 483 | "hybrid-array", | |
| 484 | + | "rand_core 0.10.1", | |
| 485 | + | ] | |
| 486 | + | ||
| 487 | + | [[package]] | |
| 488 | + | name = "ctutils" | |
| 489 | + | version = "0.4.2" | |
| 490 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 491 | + | checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" | |
| 492 | + | dependencies = [ | |
| 493 | + | "cmov", | |
| 479 | 494 | ] | |
| 480 | 495 | ||
| 481 | 496 | [[package]] | |
| @@ -1175,6 +1190,15 @@ dependencies = [ | |||
| 1175 | 1190 | ] | |
| 1176 | 1191 | ||
| 1177 | 1192 | [[package]] | |
| 1193 | + | name = "inout" | |
| 1194 | + | version = "0.2.2" | |
| 1195 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1196 | + | checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" | |
| 1197 | + | dependencies = [ | |
| 1198 | + | "hybrid-array", | |
| 1199 | + | ] | |
| 1200 | + | ||
| 1201 | + | [[package]] | |
| 1178 | 1202 | name = "ipnet" | |
| 1179 | 1203 | version = "2.11.0" | |
| 1180 | 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1421,12 +1445,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 1421 | 1445 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | |
| 1422 | 1446 | ||
| 1423 | 1447 | [[package]] | |
| 1424 | - | name = "opaque-debug" | |
| 1425 | - | version = "0.3.1" | |
| 1426 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1427 | - | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" | |
| 1428 | - | ||
| 1429 | - | [[package]] | |
| 1430 | 1448 | name = "openssl" | |
| 1431 | 1449 | version = "0.10.75" | |
| 1432 | 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1571,12 +1589,11 @@ dependencies = [ | |||
| 1571 | 1589 | ||
| 1572 | 1590 | [[package]] | |
| 1573 | 1591 | name = "poly1305" | |
| 1574 | - | version = "0.8.0" | |
| 1592 | + | version = "0.9.1" | |
| 1575 | 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1576 | - | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" | |
| 1594 | + | checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" | |
| 1577 | 1595 | dependencies = [ | |
| 1578 | - | "cpufeatures 0.2.17", | |
| 1579 | - | "opaque-debug", | |
| 1596 | + | "cpufeatures 0.3.0", | |
| 1580 | 1597 | "universal-hash", | |
| 1581 | 1598 | ] | |
| 1582 | 1599 | ||
| @@ -1638,7 +1655,7 @@ version = "0.10.2" | |||
| 1638 | 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1639 | 1656 | checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" | |
| 1640 | 1657 | dependencies = [ | |
| 1641 | - | "chacha20 0.10.1", | |
| 1658 | + | "chacha20", | |
| 1642 | 1659 | "getrandom 0.4.1", | |
| 1643 | 1660 | "rand_core 0.10.1", | |
| 1644 | 1661 | ] | |
| @@ -1648,9 +1665,6 @@ name = "rand_core" | |||
| 1648 | 1665 | version = "0.6.4" | |
| 1649 | 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1650 | 1667 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | |
| 1651 | - | dependencies = [ | |
| 1652 | - | "getrandom 0.2.17", | |
| 1653 | - | ] | |
| 1654 | 1668 | ||
| 1655 | 1669 | [[package]] | |
| 1656 | 1670 | name = "rand_core" | |
| @@ -2422,12 +2436,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | |||
| 2422 | 2436 | ||
| 2423 | 2437 | [[package]] | |
| 2424 | 2438 | name = "universal-hash" | |
| 2425 | - | version = "0.5.1" | |
| 2439 | + | version = "0.6.1" | |
| 2426 | 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2427 | - | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" | |
| 2441 | + | checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" | |
| 2428 | 2442 | dependencies = [ | |
| 2429 | - | "crypto-common 0.1.7", | |
| 2430 | - | "subtle", | |
| 2443 | + | "crypto-common 0.2.2", | |
| 2444 | + | "ctutils", | |
| 2431 | 2445 | ] | |
| 2432 | 2446 | ||
| 2433 | 2447 | [[package]] |
| @@ -21,7 +21,7 @@ testing = [] | |||
| 21 | 21 | ||
| 22 | 22 | [dependencies] | |
| 23 | 23 | # Encryption | |
| 24 | - | chacha20poly1305 = "0.10" | |
| 24 | + | chacha20poly1305 = "0.11" | |
| 25 | 25 | argon2 = "0.5" | |
| 26 | 26 | rand = "0.10" | |
| 27 | 27 | base64 = "0.22" |
| @@ -207,10 +207,10 @@ pub fn wrap_master_key(master_key: &[u8; KEY_SIZE], password: &str) -> Result<St | |||
| 207 | 207 | rand::rng().fill_bytes(&mut nonce_bytes); | |
| 208 | 208 | ||
| 209 | 209 | let cipher = XChaCha20Poly1305::new((&*wrapping_key).into()); | |
| 210 | - | let nonce = XNonce::from_slice(&nonce_bytes); | |
| 210 | + | let nonce = XNonce::from(nonce_bytes); | |
| 211 | 211 | ||
| 212 | 212 | let ciphertext = cipher | |
| 213 | - | .encrypt(nonce, master_key.as_ref()) | |
| 213 | + | .encrypt(&nonce, master_key.as_ref()) | |
| 214 | 214 | .map_err(|e| SyncKitError::Crypto(format!("wrap encrypt: {e}")))?; | |
| 215 | 215 | ||
| 216 | 216 | let envelope = KeyEnvelope { | |
| @@ -259,10 +259,11 @@ pub fn unwrap_master_key(envelope_json: &str, password: &str) -> Result<[u8; KEY | |||
| 259 | 259 | derive_wrapping_key_with_params(password, &salt, envelope.m, envelope.t, envelope.p)?; | |
| 260 | 260 | ||
| 261 | 261 | let cipher = XChaCha20Poly1305::new((&*wrapping_key).into()); | |
| 262 | - | let nonce = XNonce::from_slice(&nonce_bytes); | |
| 262 | + | let nonce = | |
| 263 | + | XNonce::try_from(&nonce_bytes[..]).expect("nonce length was validated as NONCE_SIZE above"); | |
| 263 | 264 | ||
| 264 | 265 | let mut plaintext = cipher | |
| 265 | - | .decrypt(nonce, ciphertext.as_ref()) | |
| 266 | + | .decrypt(&nonce, ciphertext.as_ref()) | |
| 266 | 267 | .map_err(|_| SyncKitError::DecryptionFailed)?; | |
| 267 | 268 | ||
| 268 | 269 | if plaintext.len() != KEY_SIZE { | |
| @@ -354,11 +355,11 @@ fn seal(plaintext: &[u8], master_key: &[u8; KEY_SIZE], aad: &[u8]) -> Result<Vec | |||
| 354 | 355 | rand::rng().fill_bytes(&mut nonce_bytes); | |
| 355 | 356 | ||
| 356 | 357 | let cipher = XChaCha20Poly1305::new(master_key.into()); | |
| 357 | - | let nonce = XNonce::from_slice(&nonce_bytes); | |
| 358 | + | let nonce = XNonce::from(nonce_bytes); | |
| 358 | 359 | ||
| 359 | 360 | let ciphertext = cipher | |
| 360 | 361 | .encrypt( | |
| 361 | - | nonce, | |
| 362 | + | &nonce, | |
| 362 | 363 | Payload { | |
| 363 | 364 | msg: plaintext, | |
| 364 | 365 | aad, | |
| @@ -380,10 +381,11 @@ fn open(blob: &[u8], master_key: &[u8; KEY_SIZE], aad: &[u8]) -> Result<Vec<u8>> | |||
| 380 | 381 | } | |
| 381 | 382 | let (nonce_bytes, ciphertext) = blob.split_at(NONCE_SIZE); | |
| 382 | 383 | let cipher = XChaCha20Poly1305::new(master_key.into()); | |
| 383 | - | let nonce = XNonce::from_slice(nonce_bytes); | |
| 384 | + | let nonce = XNonce::try_from(nonce_bytes) | |
| 385 | + | .expect("split_at(NONCE_SIZE) yields exactly NONCE_SIZE bytes"); | |
| 384 | 386 | cipher | |
| 385 | 387 | .decrypt( | |
| 386 | - | nonce, | |
| 388 | + | &nonce, | |
| 387 | 389 | Payload { | |
| 388 | 390 | msg: ciphertext, | |
| 389 | 391 | aad, | |
| @@ -1600,6 +1602,38 @@ mod tests { | |||
| 1600 | 1602 | )); | |
| 1601 | 1603 | } | |
| 1602 | 1604 | ||
| 1605 | + | /// Known-answer vectors for the XChaCha20-Poly1305 envelope, computed | |
| 1606 | + | /// outside RustCrypto: HChaCha20 implemented against the test vector in | |
| 1607 | + | /// draft-irtf-cfrg-xchacha-03 §2.2.1, then the IETF ChaCha20-Poly1305 leg | |
| 1608 | + | /// run through python `cryptography`. This is the ciphertext already on | |
| 1609 | + | /// disk and on the wire, so it must decrypt byte-for-byte forever — a | |
| 1610 | + | /// cipher-crate upgrade that silently changed the envelope would break | |
| 1611 | + | /// every existing user, and the roundtrip tests above would not notice. | |
| 1612 | + | /// | |
| 1613 | + | /// key = 00..1f, nonce = 40..57, plaintext = the string asserted below. | |
| 1614 | + | #[test] | |
| 1615 | + | fn envelope_matches_independent_known_answer() { | |
| 1616 | + | const V2_ENTRY: &str = "sk2:QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXp0BrE7uJDTbqmvHbw/MV97LMn+R4NzztBBGcK3pzRuJwl14RY250chudQ2lSbk6V"; | |
| 1617 | + | const LEGACY: &str = "QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXp0BrE7uJDTbqmvHbw/MV97LMn+R4NzztBBGcK3pzRuKvdRcL/9EeoK9LSYyzSJY7"; | |
| 1618 | + | const PLAINTEXT: &[u8] = b"synckit envelope v2 known answer"; | |
| 1619 | + | ||
| 1620 | + | let mut key = [0u8; KEY_SIZE]; | |
| 1621 | + | for (i, b) in key.iter_mut().enumerate() { | |
| 1622 | + | *b = u8::try_from(i).unwrap(); | |
| 1623 | + | } | |
| 1624 | + | ||
| 1625 | + | let ctx = AeadContext::entry("notes", "row-1"); | |
| 1626 | + | assert_eq!(decrypt_data_aad(V2_ENTRY, &key, &ctx).unwrap(), PLAINTEXT); | |
| 1627 | + | assert_eq!(decrypt_data(LEGACY, &key).unwrap(), PLAINTEXT); | |
| 1628 | + | ||
| 1629 | + | // The AAD is genuinely bound: the same bytes under a different address | |
| 1630 | + | // must fail, or the vector above would prove nothing about binding. | |
| 1631 | + | assert!(matches!( | |
| 1632 | + | decrypt_data_aad(V2_ENTRY, &key, &AeadContext::entry("notes", "row-2")), | |
| 1633 | + | Err(SyncKitError::DecryptionFailed) | |
| 1634 | + | )); | |
| 1635 | + | } | |
| 1636 | + | ||
| 1603 | 1637 | #[test] | |
| 1604 | 1638 | fn legacy_untagged_still_decrypts_under_aad_reader() { | |
| 1605 | 1639 | // A v1 (untagged, empty-AAD) payload must keep decrypting through the |