collin/anvil · 768b0c63
Bump argon2 to 0.6.0-rc.8, collapsing three duplicate crates
Collin Richards · 2026-08-25 05:46 UTC · 768b0c635e9d6a49b26c1ef6249d774220225a5c · parent d60232e6 · browse files
modifiedCargo.lock+11 −38
| ⋯ 179 unchanged lines | |||
| 180 | 180 | dependencies = [ | |
| 181 | 181 | "aes-gcm", | |
| 182 | 182 | "anvil-job", | |
| 183 | - | "argon2 0.5.3", | |
| 183 | + | "argon2", | |
| 184 | 184 | "async-trait", | |
| 185 | 185 | "base64 0.23.1", | |
| 186 | 186 | "curve25519-dalek", | |
| ⋯ 3 unchanged lines | |||
| 190 | 190 | "jaq-json", | |
| 191 | 191 | "jaq-std", | |
| 192 | 192 | "pulldown-cmark", | |
| 193 | + | "rand 0.10.2", | |
| 193 | 194 | "rusqlite", | |
| 194 | 195 | "serde", | |
| 195 | 196 | "serde_json", | |
| ⋯ 61 unchanged lines | |||
| 257 | 258 | "anvil-core", | |
| 258 | 259 | "anvil-git", | |
| 259 | 260 | "anvil-job", | |
| 260 | - | "argon2 0.5.3", | |
| 261 | + | "argon2", | |
| 261 | 262 | "axum", | |
| 262 | 263 | "axum-extra", | |
| 263 | 264 | "base64 0.23.1", | |
| ⋯ 1 unchanged line | |||
| 265 | 266 | "lru", | |
| 266 | 267 | "maud", | |
| 267 | 268 | "pulldown-cmark", | |
| 269 | + | "rand 0.10.2", | |
| 268 | 270 | "reqwest", | |
| 269 | 271 | "ring", | |
| 270 | 272 | "rsa 0.9.10", | |
| ⋯ 49 unchanged lines | |||
| 320 | 322 | ||
| 321 | 323 | [[package]] | |
| 322 | 324 | name = "argon2" | |
| 323 | - | version = "0.5.3" | |
| 324 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 325 | - | checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" | |
| 326 | - | dependencies = [ | |
| 327 | - | "base64ct", | |
| 328 | - | "blake2 0.10.6", | |
| 329 | - | "cpufeatures 0.2.17", | |
| 330 | - | "password-hash 0.5.0", | |
| 331 | - | ] | |
| 332 | - | ||
| 333 | - | [[package]] | |
| 334 | - | name = "argon2" | |
| 335 | 325 | version = "0.6.0-rc.8" | |
| 336 | 326 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 337 | 327 | checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5" | |
| 338 | 328 | dependencies = [ | |
| 339 | 329 | "base64ct", | |
| 340 | - | "blake2 0.11.0-rc.6", | |
| 330 | + | "blake2", | |
| 341 | 331 | "cpufeatures 0.3.0", | |
| 342 | - | "password-hash 0.6.1", | |
| 332 | + | "password-hash", | |
| 343 | 333 | ] | |
| 344 | 334 | ||
| 345 | 335 | [[package]] | |
| ⋯ 180 unchanged lines | |||
| 526 | 516 | ||
| 527 | 517 | [[package]] | |
| 528 | 518 | name = "blake2" | |
| 529 | - | version = "0.10.6" | |
| 530 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 531 | - | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" | |
| 532 | - | dependencies = [ | |
| 533 | - | "digest 0.10.7", | |
| 534 | - | ] | |
| 535 | - | ||
| 536 | - | [[package]] | |
| 537 | - | name = "blake2" | |
| 538 | 519 | version = "0.11.0-rc.6" | |
| 539 | 520 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 540 | 521 | checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690" | |
| ⋯ 628 unchanged lines | |||
| 1169 | 1150 | "block-buffer 0.10.4", | |
| 1170 | 1151 | "const-oid 0.9.6", | |
| 1171 | 1152 | "crypto-common 0.1.7", | |
| 1172 | - | "subtle", | |
| 1173 | 1153 | ] | |
| 1174 | 1154 | ||
| 1175 | 1155 | [[package]] | |
| ⋯ 2277 unchanged lines | |||
| 3453 | 3433 | "redox_syscall", | |
| 3454 | 3434 | "smallvec", | |
| 3455 | 3435 | "windows-link", | |
| 3456 | - | ] | |
| 3457 | - | ||
| 3458 | - | [[package]] | |
| 3459 | - | name = "password-hash" | |
| 3460 | - | version = "0.5.0" | |
| 3461 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3462 | - | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" | |
| 3463 | - | dependencies = [ | |
| 3464 | - | "base64ct", | |
| 3465 | - | "rand_core 0.6.4", | |
| 3466 | - | "subtle", | |
| 3467 | 3436 | ] | |
| 3468 | 3437 | ||
| 3469 | 3438 | [[package]] | |
| ⋯ 2 unchanged lines | |||
| 3472 | 3441 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3473 | 3442 | checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" | |
| 3474 | 3443 | dependencies = [ | |
| 3444 | + | "getrandom 0.4.3", | |
| 3475 | 3445 | "phc", | |
| 3446 | + | "rand_core 0.10.1", | |
| 3476 | 3447 | ] | |
| 3477 | 3448 | ||
| 3478 | 3449 | [[package]] | |
| ⋯ 38 unchanged lines | |||
| 3517 | 3488 | dependencies = [ | |
| 3518 | 3489 | "base64ct", | |
| 3519 | 3490 | "ctutils", | |
| 3491 | + | "getrandom 0.4.3", | |
| 3492 | + | "rand_core 0.10.1", | |
| 3520 | 3493 | ] | |
| 3521 | 3494 | ||
| 3522 | 3495 | [[package]] | |
| ⋯ 1276 unchanged lines | |||
| 4799 | 4772 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4800 | 4773 | checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3" | |
| 4801 | 4774 | dependencies = [ | |
| 4802 | - | "argon2 0.6.0-rc.8", | |
| 4775 | + | "argon2", | |
| 4803 | 4776 | "bcrypt-pbkdf", | |
| 4804 | 4777 | "ctutils", | |
| 4805 | 4778 | "ed25519-dalek", | |
| ⋯ 1228 unchanged lines | |||
modifiedCargo.toml+1 −1
| ⋯ 80 unchanged lines | |||
| 81 | 81 | ||
| 82 | 82 | aes-gcm = { version = "0.11.1" } | |
| 83 | 83 | anyhow = { version = "1" } | |
| 84 | - | argon2 = { version = "0.5", features = ["std"] } | |
| 84 | + | argon2 = { version = "0.6.0-rc.8", features = ["rand_core"] } | |
| 85 | 85 | async-trait = { version = "0.1" } | |
| 86 | 86 | axum = { version = "0.8", features = ["ws"] } | |
| 87 | 87 | axum-extra = { version = "0.12.6", features = ["cookie"] } | |
| ⋯ 38 unchanged lines | |||
modifiedclippy.toml+2 −10
| ⋯ 6 unchanged lines | |||
| 7 | 7 | # list doubles as the "what are we waiting on" record — when a bump makes an | |
| 8 | 8 | # entry unnecessary, delete it rather than leaving it to rot. | |
| 9 | 9 | # | |
| 10 | - | # Nothing here is ours to collapse today, with one exception noted below. | |
| 10 | + | # Nothing here is ours to collapse today: `argon2` was, and was taken, which | |
| 11 | + | # is what removed argon2, blake2 and password-hash from this list. | |
| 11 | 12 | ||
| 12 | 13 | allowed-duplicate-crates = [ | |
| 13 | 14 | # --- The RustCrypto `digest` 0.10 -> 0.11 seam ------------------------ | |
| ⋯ 9 unchanged lines | |||
| 23 | 24 | "generic-array", | |
| 24 | 25 | "sha1", | |
| 25 | 26 | "sha3", | |
| 26 | - | ||
| 27 | - | # `argon2` is the one duplicate we could remove ourselves: we pin 0.5 and | |
| 28 | - | # ssh-key pulls 0.6.0-rc.8. Deliberately not taken — 0.6 is a pre-release | |
| 29 | - | # and argon2 is what verifies account passwords. Moving it would also | |
| 30 | - | # collapse `blake2` and `password-hash`, which are here only because | |
| 31 | - | # argon2 0.5 holds the old copy. | |
| 32 | - | "argon2", | |
| 33 | - | "blake2", | |
| 34 | - | "password-hash", | |
| 35 | 27 | ||
| 36 | 28 | # --- Held by direct dependencies of ours ------------------------------ | |
| 37 | 29 | "base64", # axum 0.8 is on 0.22; we are on 0.23. | |
| ⋯ 12 unchanged lines | |||
modifiedcrates/anvil-core/Cargo.toml+1 −0
| ⋯ 16 unchanged lines | |||
| 17 | 17 | toasty.workspace = true | |
| 18 | 18 | rusqlite.workspace = true | |
| 19 | 19 | argon2.workspace = true | |
| 20 | + | rand.workspace = true | |
| 20 | 21 | aes-gcm.workspace = true | |
| 21 | 22 | curve25519-dalek.workspace = true | |
| 22 | 23 | base64.workspace = true | |
| ⋯ 22 unchanged lines | |||
modifiedcrates/anvil-core/src/api_tokens.rs+5 −4
| ⋯ 47 unchanged lines | |||
| 48 | 48 | name: &str, | |
| 49 | 49 | scopes: &str, | |
| 50 | 50 | ) -> Result<(ApiToken, String)> { | |
| 51 | - | use argon2::password_hash::rand_core::{ | |
| 52 | - | OsRng, | |
| 53 | - | RngCore, | |
| 51 | + | use rand::{ | |
| 52 | + | Rng, | |
| 53 | + | rand_core::UnwrapErr, | |
| 54 | + | rngs::SysRng, | |
| 54 | 55 | }; | |
| 55 | 56 | let mut raw = [0u8; 32]; | |
| 56 | - | OsRng.fill_bytes(&mut raw); | |
| 57 | + | UnwrapErr(SysRng).fill_bytes(&mut raw); | |
| 57 | 58 | let plaintext = format!( | |
| 58 | 59 | "{PREFIX}{}", | |
| 59 | 60 | raw.iter().map(|b| format!("{b:02x}")).collect::<String>() | |
| ⋯ 85 unchanged lines | |||
modifiedcrates/anvil-core/src/lib.rs+5 −4
| ⋯ 140 unchanged lines | |||
| 141 | 141 | ||
| 142 | 142 | /// Load the persistent CSRF secret, generating and saving it on first run. | |
| 143 | 143 | fn load_or_create_csrf_secret(data_dir: &std::path::Path) -> Result<[u8; 32]> { | |
| 144 | - | use argon2::password_hash::rand_core::{ | |
| 145 | - | OsRng, | |
| 146 | - | RngCore, | |
| 144 | + | use rand::{ | |
| 145 | + | Rng, | |
| 146 | + | rand_core::UnwrapErr, | |
| 147 | + | rngs::SysRng, | |
| 147 | 148 | }; | |
| 148 | 149 | ||
| 149 | 150 | let path = data_dir.join("csrf_secret"); | |
| ⋯ 5 unchanged lines | |||
| 155 | 156 | // Malformed (truncated/extended) — regenerate rather than run weak. | |
| 156 | 157 | } | |
| 157 | 158 | let mut secret = [0u8; 32]; | |
| 158 | - | OsRng.fill_bytes(&mut secret); | |
| 159 | + | UnwrapErr(SysRng).fill_bytes(&mut secret); | |
| 159 | 160 | std::fs::write(&path, secret)?; | |
| 160 | 161 | #[cfg(unix)] | |
| 161 | 162 | { | |
| ⋯ 5 unchanged lines | |||
modifiedcrates/anvil-core/src/secrets.rs+7 −4
| ⋯ 363 unchanged lines | |||
| 364 | 364 | // --- primitives ------------------------------------------------------------ | |
| 365 | 365 | ||
| 366 | 366 | fn random_bytes<const N: usize>() -> [u8; N] { | |
| 367 | - | use argon2::password_hash::rand_core::{ | |
| 368 | - | OsRng, | |
| 369 | - | RngCore, | |
| 367 | + | // `UnwrapErr(SysRng)` is the OS generator, panicking if it ever fails — | |
| 368 | + | // what `OsRng.fill_bytes` did before rand 0.10 renamed and split the two. | |
| 369 | + | use rand::{ | |
| 370 | + | Rng, | |
| 371 | + | rand_core::UnwrapErr, | |
| 372 | + | rngs::SysRng, | |
| 370 | 373 | }; | |
| 371 | 374 | let mut bytes = [0u8; N]; | |
| 372 | - | OsRng.fill_bytes(&mut bytes); | |
| 375 | + | UnwrapErr(SysRng).fill_bytes(&mut bytes); | |
| 373 | 376 | bytes | |
| 374 | 377 | } | |
| 375 | 378 | ||
| ⋯ 715 unchanged lines | |||
modifiedcrates/anvil-core/src/sessions.rs+5 −4
| 1 | 1 | //! Web login sessions: opaque random tokens stored server-side and referenced | |
| 2 | 2 | //! by a cookie. | |
| 3 | 3 | ||
| 4 | - | use argon2::password_hash::rand_core::{ | |
| 5 | - | OsRng, | |
| 6 | - | RngCore, | |
| 4 | + | use rand::{ | |
| 5 | + | Rng, | |
| 6 | + | rand_core::UnwrapErr, | |
| 7 | + | rngs::SysRng, | |
| 7 | 8 | }; | |
| 8 | 9 | ||
| 9 | 10 | use crate::{ | |
| ⋯ 10 unchanged lines | |||
| 20 | 21 | /// Generate a 256-bit random session token, hex-encoded. | |
| 21 | 22 | fn generate_token() -> String { | |
| 22 | 23 | let mut bytes = [0u8; 32]; | |
| 23 | - | let mut rng = OsRng; | |
| 24 | + | let mut rng = UnwrapErr(SysRng); | |
| 24 | 25 | rng.fill_bytes(&mut bytes); | |
| 25 | 26 | bytes.iter().map(|b| format!("{b:02x}")).collect() | |
| 26 | 27 | } | |
| ⋯ 55 unchanged lines | |||
modifiedcrates/anvil-core/src/users.rs+24 −5
| ⋯ 1 unchanged line | |||
| 2 | 2 | ||
| 3 | 3 | use argon2::{ | |
| 4 | 4 | Argon2, | |
| 5 | + | PasswordHash, | |
| 5 | 6 | password_hash::{ | |
| 6 | - | PasswordHash, | |
| 7 | 7 | PasswordHasher, | |
| 8 | 8 | PasswordVerifier, | |
| 9 | - | SaltString, | |
| 10 | - | rand_core::OsRng, | |
| 11 | 9 | }, | |
| 12 | 10 | }; | |
| 13 | 11 | ||
| ⋯ 25 unchanged lines | |||
| 39 | 37 | ]; | |
| 40 | 38 | ||
| 41 | 39 | /// Hash a plaintext password into a PHC-format Argon2 string. | |
| 40 | + | /// | |
| 41 | + | /// argon2 0.6 generates the salt itself rather than taking one, so there is no | |
| 42 | + | /// `SaltString` here any more; it still draws from the OS and still lands in | |
| 43 | + | /// the PHC string, so stored hashes are the same shape as before. | |
| 42 | 44 | pub fn hash_password(password: &str) -> Result<String> { | |
| 43 | - | let salt = SaltString::generate(&mut OsRng); | |
| 44 | 45 | Argon2::default() | |
| 45 | - | .hash_password(password.as_bytes(), &salt) | |
| 46 | + | .hash_password(password.as_bytes()) | |
| 46 | 47 | .map(|h| h.to_string()) | |
| 47 | 48 | .map_err(|e| Error::Password(e.to_string())) | |
| 48 | 49 | } | |
| ⋯ 267 unchanged lines | |||
| 316 | 317 | mod tests { | |
| 317 | 318 | use super::*; | |
| 318 | 319 | ||
| 320 | + | /// Every account provisioned before an argon2 bump has its password stored | |
| 321 | + | /// as a PHC string written by the *old* library, and the only thing that | |
| 322 | + | /// keeps those logins working is that the new one still reads them. This | |
| 323 | + | /// hash was produced by argon2 0.5 for the password below; if a future | |
| 324 | + | /// bump breaks it, every existing account is locked out and no other test | |
| 325 | + | /// here would notice, because they all hash and verify within one version. | |
| 326 | + | const PHC_FROM_ARGON2_0_5: &str = "$argon2id$v=19$m=19456,t=2,p=1$\ | |
| 327 | + | ERcTTFqHk+Nvke8V0i1j6Q$jQrVdlL3mxHw5/0culSLYUXyHX5++eQGOUI/W3txoR4"; | |
| 328 | + | ||
| 329 | + | #[test] | |
| 330 | + | fn verifies_a_hash_written_by_an_older_argon2() { | |
| 331 | + | assert!( | |
| 332 | + | verify_password(PHC_FROM_ARGON2_0_5, "correct horse battery staple").unwrap(), | |
| 333 | + | "a password hash written by argon2 0.5 no longer verifies" | |
| 334 | + | ); | |
| 335 | + | assert!(!verify_password(PHC_FROM_ARGON2_0_5, "wrong password").unwrap()); | |
| 336 | + | } | |
| 337 | + | ||
| 319 | 338 | /// A reset must persist a hash the login path accepts, and retire the old | |
| 320 | 339 | /// password. | |
| 321 | 340 | #[tokio::test] | |
| ⋯ 28 unchanged lines | |||
modifiedcrates/anvil-web/Cargo.toml+1 −0
| ⋯ 39 unchanged lines | |||
| 40 | 40 | tempfile = { version = "3" } | |
| 41 | 41 | serde_json.workspace = true | |
| 42 | 42 | argon2.workspace = true | |
| 43 | + | rand.workspace = true | |
| 43 | 44 | ssh-key = { workspace = true, features = ["ed25519"] } | |
| 44 | 45 | tokio = { workspace = true } | |
| 45 | 46 | tower = { workspace = true, features = ["util"] } | |
| ⋯ 8 unchanged lines | |||
modifiedcrates/anvil-web/tests/js_interop.rs+5 −4
| ⋯ 30 unchanged lines | |||
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | 33 | fn getrandom(buf: &mut [u8]) { | |
| 34 | - | use argon2::password_hash::rand_core::{ | |
| 35 | - | OsRng, | |
| 36 | - | RngCore, | |
| 34 | + | use rand::{ | |
| 35 | + | Rng, | |
| 36 | + | rand_core::UnwrapErr, | |
| 37 | + | rngs::SysRng, | |
| 37 | 38 | }; | |
| 38 | - | OsRng.fill_bytes(buf); | |
| 39 | + | UnwrapErr(SysRng).fill_bytes(buf); | |
| 39 | 40 | } | |
| 40 | 41 | ||
| 41 | 42 | fn node_available() -> bool { | |
| ⋯ 146 unchanged lines | |||