Skip to main content

max / makenotwork

server: bump chacha20poly1305 to 0.11 Closes the RustCrypto hash/MAC wave; sha2, sha1, and hmac landed in ad9f8e34. No API break here, since the TOTP paths already build nonces through Nonce::from rather than the from_slice that 0.11 deprecates. 1889 lib + 1190 integration tests green, clippy clean. This envelope seals every enrolled second factor at rest, so the bump is gated on a known-answer test rather than roundtrips, which re-encrypt with the same code and would pass even if the envelope changed. The vector is computed outside RustCrypto: python cryptography's ChaCha20-Poly1305 over a key derived by an independent HMAC-SHA256. Pinned in totp_secret_matches_independent_known_answer, with a wrong-secret check so it also shows the seed stays bound to SIGNING_SECRET. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-22 19:00 UTC
Commit: 18a1d8ee627bb7a9d62d59c74d95a4fc0290c380
Parent: 32a25ff
3 files changed, +42 insertions, -40 deletions
M server/Cargo.lock +22 -39
@@ -19,12 +19,12 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
19 19
20 20 [[package]]
21 21 name = "aead"
22 - version = "0.5.2"
22 + version = "0.6.1"
23 23 source = "registry+https://github.com/rust-lang/crates.io-index"
24 - checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
24 + checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
25 25 dependencies = [
26 - "crypto-common 0.1.7",
27 - "generic-array",
26 + "crypto-common 0.2.2",
27 + "inout 0.2.2",
28 28 ]
29 29
30 30 [[package]]
@@ -1590,37 +1590,26 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
1590 1590
1591 1591 [[package]]
1592 1592 name = "chacha20"
1593 - version = "0.9.1"
1594 - source = "registry+https://github.com/rust-lang/crates.io-index"
1595 - checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
1596 - dependencies = [
1597 - "cfg-if",
1598 - "cipher 0.4.4",
1599 - "cpufeatures 0.2.17",
1600 - ]
1601 -
1602 - [[package]]
1603 - name = "chacha20"
1604 1593 version = "0.10.1"
1605 1594 source = "registry+https://github.com/rust-lang/crates.io-index"
1606 1595 checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
1607 1596 dependencies = [
1608 1597 "cfg-if",
1598 + "cipher 0.5.2",
1609 1599 "cpufeatures 0.3.0",
1610 1600 "rand_core 0.10.1",
1611 1601 ]
1612 1602
1613 1603 [[package]]
1614 1604 name = "chacha20poly1305"
1615 - version = "0.10.1"
1605 + version = "0.11.0"
1616 1606 source = "registry+https://github.com/rust-lang/crates.io-index"
1617 - checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
1607 + checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb"
1618 1608 dependencies = [
1619 1609 "aead",
1620 - "chacha20 0.9.1",
1621 - "cipher 0.4.4",
1610 + "chacha20",
1611 + "cipher 0.5.2",
1622 1612 "poly1305",
1623 - "zeroize",
1624 1613 ]
1625 1614
1626 1615 [[package]]
@@ -1645,7 +1634,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
1645 1634 dependencies = [
1646 1635 "crypto-common 0.1.7",
1647 1636 "inout 0.1.4",
1648 - "zeroize",
1649 1637 ]
1650 1638
1651 1639 [[package]]
@@ -1654,6 +1642,7 @@ version = "0.5.2"
1654 1642 source = "registry+https://github.com/rust-lang/crates.io-index"
1655 1643 checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
1656 1644 dependencies = [
1645 + "block-buffer 0.12.1",
1657 1646 "crypto-common 0.2.2",
1658 1647 "inout 0.2.2",
1659 1648 ]
@@ -2175,7 +2164,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2175 2164 checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
2176 2165 dependencies = [
2177 2166 "generic-array",
2178 - "rand_core 0.6.4",
2179 2167 "typenum",
2180 2168 ]
2181 2169
@@ -2185,7 +2173,9 @@ version = "0.2.2"
2185 2173 source = "registry+https://github.com/rust-lang/crates.io-index"
2186 2174 checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
2187 2175 dependencies = [
2176 + "getrandom 0.4.3",
2188 2177 "hybrid-array",
2178 + "rand_core 0.10.1",
2189 2179 ]
2190 2180
2191 2181 [[package]]
@@ -4854,12 +4844,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4854 4844 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
4855 4845
4856 4846 [[package]]
4857 - name = "opaque-debug"
4858 - version = "0.3.1"
4859 - source = "registry+https://github.com/rust-lang/crates.io-index"
4860 - checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
4861 -
4862 - [[package]]
4863 4847 name = "openssl"
4864 4848 version = "0.10.81"
4865 4849 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5287,12 +5271,11 @@ dependencies = [
5287 5271
5288 5272 [[package]]
5289 5273 name = "poly1305"
5290 - version = "0.8.0"
5274 + version = "0.9.1"
5291 5275 source = "registry+https://github.com/rust-lang/crates.io-index"
5292 - checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
5276 + checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
5293 5277 dependencies = [
5294 - "cpufeatures 0.2.17",
5295 - "opaque-debug",
5278 + "cpufeatures 0.3.0",
5296 5279 "universal-hash",
5297 5280 ]
5298 5281
@@ -5517,9 +5500,9 @@ dependencies = [
5517 5500
5518 5501 [[package]]
5519 5502 name = "pulldown-cmark"
5520 - version = "0.12.2"
5503 + version = "0.13.4"
5521 5504 source = "registry+https://github.com/rust-lang/crates.io-index"
5522 - checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
5505 + checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
5523 5506 dependencies = [
5524 5507 "bitflags 2.13.0",
5525 5508 "getopts",
@@ -5720,7 +5703,7 @@ version = "0.10.2"
5720 5703 source = "registry+https://github.com/rust-lang/crates.io-index"
5721 5704 checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
5722 5705 dependencies = [
5723 - "chacha20 0.10.1",
5706 + "chacha20",
5724 5707 "getrandom 0.4.3",
5725 5708 "rand_core 0.10.1",
5726 5709 ]
@@ -7949,12 +7932,12 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
7949 7932
7950 7933 [[package]]
7951 7934 name = "universal-hash"
7952 - version = "0.5.1"
7935 + version = "0.6.1"
7953 7936 source = "registry+https://github.com/rust-lang/crates.io-index"
7954 - checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
7937 + checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
7955 7938 dependencies = [
7956 - "crypto-common 0.1.7",
7957 - "subtle",
7939 + "crypto-common 0.2.2",
7940 + "ctutils",
7958 7941 ]
7959 7942
7960 7943 [[package]]
@@ -75,7 +75,7 @@ hmac = "0.13.0"
75 75 sha1 = "0.11.0"
76 76 sha2 = "0.11.0"
77 77 subtle = "2.6"
78 - chacha20poly1305 = "0.10.1"
78 + chacha20poly1305 = "0.11.0"
79 79 hex = "0.4.3"
80 80 base64 = "0.22.1"
81 81
@@ -319,6 +319,25 @@ mod tests {
319 319
320 320 // ── TOTP secret encryption at rest ──
321 321
322 + /// Known-answer vector for the at-rest TOTP envelope, computed outside
323 + /// RustCrypto (python `cryptography`'s ChaCha20-Poly1305, with the key
324 + /// derived by an independent HMAC-SHA256). Every enrolled second factor in
325 + /// the database is sealed this way, so the envelope must keep decrypting
326 + /// byte-for-byte across cipher upgrades. The roundtrip tests below cannot
327 + /// catch a changed envelope, since they re-encrypt with the same code.
328 + #[test]
329 + fn totp_secret_matches_independent_known_answer() {
330 + const STORED: &str = "enc:v1:EBESExQVFhcYGRobIDLGTuOMhI+7NitFN57N5vREAFNm152FpA/N06cmy8o=";
331 + let signing_secret = "test-signing-secret-for-known-answer";
332 + assert_eq!(
333 + decrypt_totp_secret(STORED, signing_secret).unwrap(),
334 + "JBSWY3DPEHPK3PXP"
335 + );
336 + // A different signing secret must fail, or the vector would not show
337 + // that the seed is actually bound to SIGNING_SECRET.
338 + assert!(decrypt_totp_secret(STORED, "some-other-signing-secret").is_err());
339 + }
340 +
322 341 #[test]
323 342 fn totp_secret_round_trips() {
324 343 let secret = "JBSWY3DPEHPK3PXP";