oxedyne/fe2o3/fe2o3_hash/src/kdf.rs
47.0 KiB, 224 runs
created by r1870400018:353, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! This crate provides a concrete interface for multiple Key Derivation Function algorithms. |
| 2 | //! Currently it uses only one, Argon2. The Argon2 tag depends on the lane count `p` by design, so |
| 3 | //! a tag produced with `p = 4` differs from one produced with `p = 1` for otherwise identical |
| 4 | //! inputs. Every parameter that changes the tag must therefore survive a round trip through the |
| 5 | //! encoded configuration string, or the derived key will silently differ. The default here is a |
| 6 | //! single lane, which the Open Worldwide Application Security Project (OWASP) recommends for |
| 7 | //! Argon2id, but the encoding does not assume it. |
| 8 | //! |
| 9 | //! # Encoded strings |
| 10 | //! |
| 11 | //! `encode_to_string` emits the Password Hashing Competition (PHC) string format exactly, as |
| 12 | //! `$argon2id$v=19$m=<mem_cost>,t=<time_cost>,p=<lanes>$<salt>$<hash>`, so that other Argon2 |
| 13 | //! implementations can read it. The tag length is implied by the length of the encoded hash. |
| 14 | //! |
| 15 | //! `encode_cfg_to_string` emits the same string sans hash. PHC defines no hash-less form, and |
| 16 | //! without the hash the tag length would be lost, so the tag length is carried explicitly in an |
| 17 | //! `l=<hash_length>` option, as `$argon2id$v=19$m=...,t=...,p=...,l=32$<salt>`. Associated data, |
| 18 | //! when present, is carried in the `data=<base64>` option used by the reference implementation. |
| 19 | //! |
| 20 | //! The secret (pepper) is deliberately never encoded. A caller using one must set it on the |
| 21 | //! decoded state out of band; the encoders return an error rather than drop it silently. |
| 22 | //! |
| 23 | use oxedyne_fe2o3_core::{ |
| 24 | prelude::*, |
| 25 | alt::{ |
| 26 | Alt, |
| 27 | DefAlt, |
| 28 | }, |
| 29 | mem::Extract, |
| 30 | rand::Rand, |
| 31 | }; |
| 32 | use oxedyne_fe2o3_jdat::{ |
| 33 | prelude::*, |
| 34 | try_extract_tup2dat, |
| 35 | tup2dat, |
| 36 | }; |
| 37 | use oxedyne_fe2o3_iop_hash::kdf::KeyDeriver; |
| 38 | use oxedyne_fe2o3_namex::id::{ |
| 39 | LocalId, |
| 40 | InNamex, |
| 41 | NamexId, |
| 42 | }; |
| 43 | |
| 44 | use std::{ |
| 45 | fmt, |
| 46 | str, |
| 47 | }; |
| 48 | |
| 49 | use argon2; |
| 50 | use base64; |
| 51 | |
| 52 | |
| 53 | #[derive(Clone, Debug, Default)] |
| 54 | pub struct KeyDerivConfig { |
| 55 | id: LocalId, // Key derivation function id. |
| 56 | cfg: String, // String encoded configuration information. |
| 57 | } |
| 58 | |
| 59 | impl ToDat for KeyDerivConfig { |
| 60 | fn to_dat(&self) -> Outcome<Dat> { |
| 61 | Ok(tup2dat![ |
| 62 | res!(self.id.to_dat()), |
| 63 | Dat::Str(self.cfg.clone()), |
| 64 | ]) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | impl FromDat for KeyDerivConfig { |
| 69 | fn from_dat(dat: Dat) -> Outcome<Self> { |
| 70 | let mut result = Self::default(); |
| 71 | let mut v = try_extract_tup2dat!(dat); |
| 72 | result.id = res!(LocalId::from_dat(v[0].extract())); |
| 73 | result.cfg = try_extract_dat!(v[1].extract(), Str); |
| 74 | Ok(result) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | impl KeyDerivConfig { |
| 79 | |
| 80 | pub fn new(id: LocalId, cfg: String,) -> Self { |
| 81 | Self { |
| 82 | id, |
| 83 | cfg, |
| 84 | } |
| 85 | } |
| 86 | pub fn id(&self) -> &LocalId { &self.id } |
| 87 | pub fn config(&self) -> &String { &self.cfg } |
| 88 | } |
| 89 | |
| 90 | #[derive(Clone, Eq, PartialEq)] |
| 91 | pub enum KeyDerivationScheme { |
| 92 | Argon2(Argon2State), |
| 93 | } |
| 94 | |
| 95 | impl fmt::Display for KeyDerivationScheme { |
| 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 97 | write!(f, "{:?}", self) |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | impl fmt::Debug for KeyDerivationScheme { |
| 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 103 | match self { |
| 104 | Self::Argon2(state) => { |
| 105 | let suffix = match state.version { |
| 106 | argon2::Version::Version10 => "_v0x10", |
| 107 | argon2::Version::Version13 => "_v0x13", |
| 108 | }; |
| 109 | let mut variant = state.variant.as_uppercase_str().to_string(); |
| 110 | variant.push_str(suffix); |
| 111 | write!(f, "{}", variant) |
| 112 | }, |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | impl InNamex for KeyDerivationScheme { |
| 118 | |
| 119 | fn name_id(&self) -> Outcome<NamexId> { |
| 120 | Ok(match self { |
| 121 | Self::Argon2(Argon2State { |
| 122 | variant: argon2::Variant::Argon2d, |
| 123 | version: argon2::Version::Version10, |
| 124 | .. |
| 125 | }) => res!(NamexId::try_from("sl6UqXw8nwjEzDMyO9TMlCzwUKtNDHgpDq+DOYjUukU=")), |
| 126 | // |
| 127 | Self::Argon2(Argon2State { |
| 128 | variant: argon2::Variant::Argon2i, |
| 129 | version: argon2::Version::Version10, |
| 130 | .. |
| 131 | }) => res!(NamexId::try_from("cu9pC9q0wONz80OCjaxtMJiyv4zqMUuljhDKMOiUE4o=")), |
| 132 | // |
| 133 | Self::Argon2(Argon2State { |
| 134 | variant: argon2::Variant::Argon2id, |
| 135 | version: argon2::Version::Version10, |
| 136 | .. |
| 137 | }) => res!(NamexId::try_from("D4NkV8upthkHwlPrkfq/wqo+S4dnXwU7EkM4vg4h4jo=")), |
| 138 | // |
| 139 | Self::Argon2(Argon2State { |
| 140 | variant: argon2::Variant::Argon2d, |
| 141 | version: argon2::Version::Version13, |
| 142 | .. |
| 143 | }) => res!(NamexId::try_from("bbj8wR0HwO6JJoWDWgauLWOacUfLfJZVnoWEyTYIfqI=")), |
| 144 | // |
| 145 | Self::Argon2(Argon2State { |
| 146 | variant: argon2::Variant::Argon2i, |
| 147 | version: argon2::Version::Version13, |
| 148 | .. |
| 149 | }) => res!(NamexId::try_from("6UQjGIfYSHD7/2mcr27mtB8B9IyQBRpye0ABSH9/YCo=")), |
| 150 | // |
| 151 | Self::Argon2(Argon2State { |
| 152 | variant: argon2::Variant::Argon2id, |
| 153 | version: argon2::Version::Version13, |
| 154 | .. |
| 155 | }) => res!(NamexId::try_from("M/dnApVP4FC91bbNYeKy73V6j3NVfwW7qcVspx53zl8=")), |
| 156 | }) |
| 157 | } |
| 158 | |
| 159 | fn local_id(&self) -> LocalId { |
| 160 | match self { |
| 161 | Self::Argon2(Argon2State { |
| 162 | variant: argon2::Variant::Argon2d, |
| 163 | version: argon2::Version::Version10, |
| 164 | .. |
| 165 | }) => LocalId(1), |
| 166 | // |
| 167 | Self::Argon2(Argon2State { |
| 168 | variant: argon2::Variant::Argon2i, |
| 169 | version: argon2::Version::Version10, |
| 170 | .. |
| 171 | }) => LocalId(2), |
| 172 | // |
| 173 | Self::Argon2(Argon2State { |
| 174 | variant: argon2::Variant::Argon2id, |
| 175 | version: argon2::Version::Version10, |
| 176 | .. |
| 177 | }) => LocalId(3), |
| 178 | // |
| 179 | Self::Argon2(Argon2State { |
| 180 | variant: argon2::Variant::Argon2d, |
| 181 | version: argon2::Version::Version13, |
| 182 | .. |
| 183 | }) => LocalId(4), |
| 184 | // |
| 185 | Self::Argon2(Argon2State { |
| 186 | variant: argon2::Variant::Argon2i, |
| 187 | version: argon2::Version::Version13, |
| 188 | .. |
| 189 | }) => LocalId(5), |
| 190 | // |
| 191 | Self::Argon2(Argon2State { |
| 192 | variant: argon2::Variant::Argon2id, |
| 193 | version: argon2::Version::Version13, |
| 194 | .. |
| 195 | }) => LocalId(6), |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | fn assoc_names_base64( |
| 200 | gname: &'static str, |
| 201 | ) |
| 202 | -> Outcome<Option<Vec<( |
| 203 | &'static str, |
| 204 | &'static str, |
| 205 | )>>> |
| 206 | { |
| 207 | let ids = match gname { |
| 208 | "schemes" => [ |
| 209 | ("Argon2d_v0x10", "sl6UqXw8nwjEzDMyO9TMlCzwUKtNDHgpDq+DOYjUukU="), |
| 210 | ("Argon2i_v0x10", "cu9pC9q0wONz80OCjaxtMJiyv4zqMUuljhDKMOiUE4o="), |
| 211 | ("Argon2id_v0x10", "D4NkV8upthkHwlPrkfq/wqo+S4dnXwU7EkM4vg4h4jo="), |
| 212 | ("Argon2d_v0x13", "bbj8wR0HwO6JJoWDWgauLWOacUfLfJZVnoWEyTYIfqI="), |
| 213 | ("Argon2i_v0x13", "6UQjGIfYSHD7/2mcr27mtB8B9IyQBRpye0ABSH9/YCo="), |
| 214 | ("Argon2id_v0x13", "M/dnApVP4FC91bbNYeKy73V6j3NVfwW7qcVspx53zl8="), |
| 215 | ], |
| 216 | _ => return Err(err!( |
| 217 | "The Namex group name '{}' is not recognised for KeyDerivationScheme.", gname; |
| 218 | Invalid, Input)), |
| 219 | }; |
| 220 | Ok(if ids.len() == 0 { |
| 221 | None |
| 222 | } else { |
| 223 | Some(ids.to_vec()) |
| 224 | }) |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | impl str::FromStr for KeyDerivationScheme { |
| 229 | type Err = Error<ErrTag>; |
| 230 | |
| 231 | fn from_str(name: &str) -> std::result::Result<Self, Self::Err> { |
| 232 | match name { |
| 233 | "Argon2d_v0x10" => Self::default_argon2("Argon2d", 0x10), |
| 234 | "Argon2i_v0x10" => Self::default_argon2("Argon2i", 0x10), |
| 235 | "Argon2id_v0x10" => Self::default_argon2("Argon2id", 0x10), |
| 236 | "Argon2d_v0x13" => Self::default_argon2("Argon2d", 0x13), |
| 237 | "Argon2i_v0x13" => Self::default_argon2("Argon2i", 0x13), |
| 238 | "Argon2id_v0x13" => Self::default_argon2("Argon2id", 0x13), |
| 239 | _ => Err(err!( |
| 240 | "The key derivation scheme '{}' is not recognised.", name; |
| 241 | Invalid, Input)), |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | impl TryFrom<LocalId> for KeyDerivationScheme { |
| 247 | type Error = Error<ErrTag>; |
| 248 | |
| 249 | fn try_from(n: LocalId) -> std::result::Result<Self, Self::Error> { |
| 250 | match n { |
| 251 | LocalId(1) => Self::default_argon2("Argon2d", 0x10), |
| 252 | LocalId(2) => Self::default_argon2("Argon2i", 0x10), |
| 253 | LocalId(3) => Self::default_argon2("Argon2id", 0x10), |
| 254 | LocalId(4) => Self::default_argon2("Argon2d", 0x13), |
| 255 | LocalId(5) => Self::default_argon2("Argon2i", 0x13), |
| 256 | LocalId(6) => Self::default_argon2("Argon2id", 0x13), |
| 257 | _ => Err(err!( |
| 258 | "The key derivation scheme with local id {} is not recognised.", n; |
| 259 | Invalid, Input)), |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | impl KeyDerivationScheme { |
| 265 | |
| 266 | /// Default `KeyDerivationScheme::Argon2` includes a new random salt. |
| 267 | pub fn default_argon2( |
| 268 | variant: &str, |
| 269 | version: u32, |
| 270 | ) |
| 271 | -> Outcome<Self> |
| 272 | { |
| 273 | let mut state = Argon2State::default(); |
| 274 | state.variant = res!(argon2::Variant::from_str(variant)); |
| 275 | state.version = res!(argon2::Version::from_u32(version)); |
| 276 | Ok(Self::Argon2(state)) |
| 277 | } |
| 278 | |
| 279 | pub fn new_argon2( |
| 280 | variant: &str, |
| 281 | version: u32, |
| 282 | mem_cost: u32, |
| 283 | time_cost: u32, |
| 284 | salt_length: usize, |
| 285 | hash_length: u32, |
| 286 | ) |
| 287 | -> Outcome<Self> |
| 288 | { |
| 289 | let mut kdf = Self::Argon2(Argon2State { |
| 290 | hash_length, |
| 291 | lanes: 1, |
| 292 | mem_cost, |
| 293 | time_cost, |
| 294 | variant: res!(argon2::Variant::from_str(variant)), |
| 295 | version: res!(argon2::Version::from_u32(version)), |
| 296 | ..Default::default() |
| 297 | }); |
| 298 | res!(kdf.set_rand_salt(salt_length)); |
| 299 | Ok(kdf) |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 304 | pub struct Argon2State { |
| 305 | // Direct copy of argon2::Config<'a> |
| 306 | pub ad: Vec<u8>, |
| 307 | pub hash_length: u32, |
| 308 | pub lanes: u32, |
| 309 | pub mem_cost: u32, |
| 310 | pub secret: Vec<u8>, |
| 311 | pub time_cost: u32, |
| 312 | pub variant: argon2::Variant, |
| 313 | pub version: argon2::Version, |
| 314 | // Extra |
| 315 | pub salt: Vec<u8>, |
| 316 | pub hash: Option<Vec<u8>>, |
| 317 | } |
| 318 | |
| 319 | impl Default for Argon2State { |
| 320 | fn default() -> Self { |
| 321 | let mut salt = [0u8; 16]; |
| 322 | Rand::fill_u8(&mut salt); |
| 323 | Self { |
| 324 | ad: Vec::new(), |
| 325 | hash_length: 32, |
| 326 | lanes: 1, |
| 327 | mem_cost: 65536, |
| 328 | secret: Vec::new(), |
| 329 | time_cost: 5, |
| 330 | variant: argon2::Variant::Argon2id, |
| 331 | version: argon2::Version::Version13, |
| 332 | salt: salt.to_vec(), |
| 333 | hash: None, |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | impl Argon2State { |
| 339 | |
| 340 | /// Creates an Argon2 state with the given parameters, all of which change the resulting tag. |
| 341 | pub fn new( |
| 342 | variant: &str, |
| 343 | version: u32, |
| 344 | mem_cost: u32, |
| 345 | time_cost: u32, |
| 346 | lanes: u32, |
| 347 | hash_length: u32, |
| 348 | salt: Vec<u8>, |
| 349 | ) |
| 350 | -> Outcome<Self> |
| 351 | { |
| 352 | Ok(Self { |
| 353 | hash_length, |
| 354 | lanes, |
| 355 | mem_cost, |
| 356 | time_cost, |
| 357 | variant: res!(argon2::Variant::from_str(variant)), |
| 358 | version: res!(argon2::Version::from_u32(version)), |
| 359 | salt, |
| 360 | ..Default::default() |
| 361 | }) |
| 362 | } |
| 363 | |
| 364 | pub fn to_argon2_config<'a>(&'a self) -> argon2::Config<'a> { |
| 365 | argon2::Config { |
| 366 | ad: &self.ad, |
| 367 | hash_length: self.hash_length, |
| 368 | lanes: self.lanes, |
| 369 | mem_cost: self.mem_cost, |
| 370 | secret: &self.secret, |
| 371 | time_cost: self.time_cost, |
| 372 | variant: self.variant.clone(), |
| 373 | version: self.version.clone(), |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | /// Decodes an Argon2 string, returning an error rather than silently defaulting any parameter |
| 378 | /// that changes the tag. The `argon2::encoding` module is private, so the decoding must be |
| 379 | /// replicated here. The secret, which is never encoded, is left untouched on `self`. |
| 380 | pub fn from_argon2_string( |
| 381 | &mut self, |
| 382 | encoded: &str, |
| 383 | expect_hash: bool, |
| 384 | ) |
| 385 | -> Outcome<()> |
| 386 | { |
| 387 | let body = match encoded.strip_prefix('$') { |
| 388 | Some(s) => s, |
| 389 | None => return Err(err!( |
| 390 | "Encoded Argon2 string '{}' does not begin with a '$'.", encoded; |
| 391 | Invalid, Input)), |
| 392 | }; |
| 393 | let (n_complete, n_v0x10) = if expect_hash { (5, 4) } else { (4, 3) }; |
| 394 | let parts: Vec<&str> = body.split('$').collect(); |
| 395 | // The version field is absent in the original v0x10 encoding. |
| 396 | let (variant_str, version, opts_str, salt_str, hash_str) = if parts.len() == n_complete { |
| 397 | let vstr = res!(Self::extract_value(parts[1], "v", "version")); |
| 398 | ( |
| 399 | parts[0], |
| 400 | res!(argon2::Version::from_str(vstr)), |
| 401 | parts[2], |
| 402 | parts[3], |
| 403 | if expect_hash { Some(parts[4]) } else { None }, |
| 404 | ) |
| 405 | } else if parts.len() == n_v0x10 { |
| 406 | ( |
| 407 | parts[0], |
| 408 | argon2::Version::Version10, |
| 409 | parts[1], |
| 410 | parts[2], |
| 411 | if expect_hash { Some(parts[3]) } else { None }, |
| 412 | ) |
| 413 | } else { |
| 414 | return Err(err!( |
| 415 | "The Argon2 string '{}' should have {} or {} parts separated by the '$' \ |
| 416 | character, {} were found.", encoded, n_v0x10, n_complete, parts.len(); |
| 417 | Decode, String, Invalid, Input)); |
| 418 | }; |
| 419 | let opts = res!(Self::extract_options(opts_str)); |
| 420 | let salt = res!(base64::decode(salt_str)); |
| 421 | let hash = match hash_str { |
| 422 | Some(s) => Some(res!(base64::decode(s))), |
| 423 | None => None, |
| 424 | }; |
| 425 | // The tag length is implied by the hash when there is one, and carried in the 'l' option |
| 426 | // when there is not. A configuration string with neither is the format the previous |
| 427 | // release wrote, which never emitted 'l' at all: those strings are in every wallet in the |
| 428 | // field, and a wallet cannot be rebuilt from source, so refusing to read one would lock its |
| 429 | // owner out of their own master key. The tag length then stays as the state already has |
| 430 | // it, which is what the old decoder did, and so derives the key that string has always |
| 431 | // meant. |
| 432 | let hash_length = match (&hash, opts.hash_length) { |
| 433 | (Some(h), Some(l)) => { |
| 434 | if (h.len() as u32) != l { |
| 435 | return Err(err!( |
| 436 | "The Argon2 string '{}' declares a hash length of {} but encodes a hash \ |
| 437 | of {} bytes.", encoded, l, h.len(); |
| 438 | Decode, String, Mismatch, Invalid, Input)); |
| 439 | } |
| 440 | l |
| 441 | }, |
| 442 | (Some(h), None) => h.len() as u32, |
| 443 | (None, Some(l)) => l, |
| 444 | (None, None) => self.hash_length, |
| 445 | }; |
| 446 | self.variant = res!(argon2::Variant::from_str(variant_str)); |
| 447 | self.version = version; |
| 448 | self.mem_cost = opts.mem_cost; |
| 449 | self.time_cost = opts.time_cost; |
| 450 | self.lanes = opts.lanes; |
| 451 | self.hash_length = hash_length; |
| 452 | self.ad = opts.ad; |
| 453 | self.salt = salt; |
| 454 | self.hash = hash; |
| 455 | Ok(()) |
| 456 | } |
| 457 | |
| 458 | /// Returns an error if a secret is present, since the encoded string must never carry it. |
| 459 | fn require_no_secret(&self) -> Outcome<()> { |
| 460 | if !self.secret.is_empty() { |
| 461 | return Err(err!( |
| 462 | "This Argon2 state carries a secret of {} bytes. A secret is never written to the \ |
| 463 | encoded string, and encoding it away silently would derive a different key on \ |
| 464 | decoding. Supply the secret out of band on the decoded state instead.", |
| 465 | self.secret.len(); |
| 466 | Invalid, Input, Security)); |
| 467 | } |
| 468 | Ok(()) |
| 469 | } |
| 470 | |
| 471 | /// Encodes the associated data, when present, as the `data` option used by the reference |
| 472 | /// implementation, including the leading comma. |
| 473 | fn encode_ad(&self) -> String { |
| 474 | if self.ad.is_empty() { |
| 475 | String::new() |
| 476 | } else { |
| 477 | fmt!(",data={}", base64::encode_config(&self.ad, base64::STANDARD_NO_PAD)) |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | fn extract_value<'a>( |
| 482 | s: &'a str, |
| 483 | name: &'static str, |
| 484 | err_str: &'static str, |
| 485 | ) |
| 486 | -> Outcome<&'a str> |
| 487 | { |
| 488 | let parts: Vec<&str> = s.split('=').collect(); |
| 489 | if parts.len() == 2 { |
| 490 | if parts[0] == name { |
| 491 | Ok(parts[1]) |
| 492 | } else { |
| 493 | Err(err!( |
| 494 | "The Argon2 {} substring key must be '{}', found '{}'.", err_str, name, parts[0]; |
| 495 | Missing, Decode, String, Invalid, Input)) |
| 496 | } |
| 497 | } else { |
| 498 | Err(err!( |
| 499 | "The Argon2 {} substring '{}' should have 2 parts separated by the '=' \ |
| 500 | character. {} were found.", err_str, s, parts.len(); |
| 501 | Decode, String, Invalid, Input)) |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | /// Decodes the comma separated options substring. The mandatory `m`, `t` and `p` come first, |
| 506 | /// in that order, and may be followed by the optional `l` and `data`. Any other option is an |
| 507 | /// error, because an option we do not understand may well be one that changes the tag. |
| 508 | fn extract_options(s: &str) -> Outcome<Argon2Options> { |
| 509 | let parts: Vec<&str> = s.split(',').collect(); |
| 510 | if parts.len() < 3 { |
| 511 | return Err(err!( |
| 512 | "The Argon2 options substring '{}' should have at least the 3 parts 'm', 't' and \ |
| 513 | 'p' separated by the ',' character. {} were found.", s, parts.len(); |
| 514 | Decode, String, Missing, Invalid, Input)); |
| 515 | } |
| 516 | let mstr = res!(Self::extract_value(parts[0], "m", "mem_cost")); |
| 517 | let tstr = res!(Self::extract_value(parts[1], "t", "time_cost")); |
| 518 | let pstr = res!(Self::extract_value(parts[2], "p", "lanes")); |
| 519 | let mut result = Argon2Options { |
| 520 | mem_cost: res!(mstr.parse::<u32>()), |
| 521 | time_cost: res!(tstr.parse::<u32>()), |
| 522 | lanes: res!(pstr.parse::<u32>()), |
| 523 | hash_length: None, |
| 524 | ad: Vec::new(), |
| 525 | }; |
| 526 | let mut ad_seen = false; |
| 527 | for part in &parts[3..] { |
| 528 | let kv: Vec<&str> = part.split('=').collect(); |
| 529 | if kv.len() != 2 { |
| 530 | return Err(err!( |
| 531 | "The Argon2 option '{}' should have 2 parts separated by the '=' character. \ |
| 532 | {} were found.", part, kv.len(); |
| 533 | Decode, String, Invalid, Input)); |
| 534 | } |
| 535 | match kv[0] { |
| 536 | "l" => { |
| 537 | if result.hash_length.is_some() { |
| 538 | return Err(err!( |
| 539 | "The Argon2 options substring '{}' repeats the 'l' option.", s; |
| 540 | Decode, String, Duplicate, Invalid, Input)); |
| 541 | } |
| 542 | result.hash_length = Some(res!(kv[1].parse::<u32>())); |
| 543 | }, |
| 544 | "data" => { |
| 545 | if ad_seen { |
| 546 | return Err(err!( |
| 547 | "The Argon2 options substring '{}' repeats the 'data' option.", s; |
| 548 | Decode, String, Duplicate, Invalid, Input)); |
| 549 | } |
| 550 | result.ad = res!(base64::decode(kv[1])); |
| 551 | ad_seen = true; |
| 552 | }, |
| 553 | _ => return Err(err!( |
| 554 | "The Argon2 option key '{}' in options substring '{}' is not recognised. An \ |
| 555 | unrecognised option may change the derived key, so it cannot be ignored.", |
| 556 | kv[0], s; |
| 557 | Decode, String, Unknown, Invalid, Input)), |
| 558 | } |
| 559 | } |
| 560 | Ok(result) |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | /// The decoded options substring of an Argon2 encoded string. |
| 565 | struct Argon2Options { |
| 566 | mem_cost: u32, |
| 567 | time_cost: u32, |
| 568 | lanes: u32, |
| 569 | hash_length: Option<u32>, // Absent from a string that carries a hash. |
| 570 | ad: Vec<u8>, |
| 571 | } |
| 572 | |
| 573 | impl KeyDeriver for KeyDerivationScheme { |
| 574 | |
| 575 | fn get_hash(&self) -> Outcome<&[u8]> { |
| 576 | match self { |
| 577 | Self::Argon2(state) => match &state.hash { |
| 578 | Some(hash) => return Ok(&hash[..]), |
| 579 | None => (), |
| 580 | }, |
| 581 | } |
| 582 | Err(err!( |
| 583 | "{}: Expected hash to be be present, found none.", self; |
| 584 | Data, Missing)) |
| 585 | } |
| 586 | |
| 587 | fn set_rand_salt(&mut self, n: usize) -> Outcome<()> { |
| 588 | let mut salt = vec![0u8; n]; |
| 589 | Rand::fill_u8(&mut salt); |
| 590 | match self { |
| 591 | Self::Argon2(state) => state.salt = salt, |
| 592 | } |
| 593 | Ok(()) |
| 594 | } |
| 595 | |
| 596 | fn derive(&mut self, pass: &[u8]) -> Outcome<()> { |
| 597 | match self { |
| 598 | Self::Argon2(state) => match argon2::hash_raw( |
| 599 | pass, |
| 600 | &state.salt, |
| 601 | &state.to_argon2_config(), |
| 602 | ) { |
| 603 | Ok(hash) => { |
| 604 | state.hash = Some(hash); |
| 605 | Ok(()) |
| 606 | }, |
| 607 | Err(e) => Err(err!(e, "While performing Argon2 hash."; Invalid, Input)), |
| 608 | }, |
| 609 | } |
| 610 | } |
| 611 | |
| 612 | fn verify(&self, pass: &[u8]) -> Outcome<bool> { |
| 613 | match self { |
| 614 | Self::Argon2(state) => match &state.hash { |
| 615 | Some(hash) => Ok(res!(argon2::verify_raw( |
| 616 | pass, |
| 617 | &state.salt, |
| 618 | &hash, |
| 619 | &state.to_argon2_config(), |
| 620 | ))), |
| 621 | None => Err(err!("Hash has not been created."; Missing)), |
| 622 | }, |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | /// Encodes the state and its hash in the PHC string format, which other Argon2 implementations |
| 627 | /// can read. The tag length is implied by the length of the encoded hash. |
| 628 | fn encode_to_string(&self) -> Outcome<String> { |
| 629 | match self { |
| 630 | Self::Argon2(state) => match &state.hash { |
| 631 | Some(hash) => { |
| 632 | // The encoding function is copied from argon2 to avoid using argon2::Context, |
| 633 | // which requires the password. |
| 634 | res!(state.require_no_secret()); |
| 635 | if (hash.len() as u32) != state.hash_length { |
| 636 | return Err(err!( |
| 637 | "This Argon2 state declares a hash length of {} but holds a hash of \ |
| 638 | {} bytes.", state.hash_length, hash.len(); |
| 639 | Bug, Mismatch, Invalid)); |
| 640 | } |
| 641 | Ok(fmt!( |
| 642 | "${}$v={}$m={},t={},p={}{}${}${}", |
| 643 | state.variant, |
| 644 | state.version, |
| 645 | state.mem_cost, |
| 646 | state.time_cost, |
| 647 | state.lanes, |
| 648 | state.encode_ad(), |
| 649 | base64::encode_config(&state.salt, base64::STANDARD_NO_PAD), |
| 650 | base64::encode_config(&hash, base64::STANDARD_NO_PAD), |
| 651 | )) |
| 652 | }, |
| 653 | None => Err(err!("Hash has not been created."; Missing)), |
| 654 | }, |
| 655 | } |
| 656 | } |
| 657 | |
| 658 | /// Encodes the state sans hash. Since PHC defines no hash-less form, and the tag length can |
| 659 | /// no longer be implied by a hash, it is carried explicitly in the `l` option. |
| 660 | fn encode_cfg_to_string(&self) -> Outcome<String> { |
| 661 | match self { |
| 662 | Self::Argon2(state) => { |
| 663 | res!(state.require_no_secret()); |
| 664 | Ok(fmt!( |
| 665 | "${}$v={}$m={},t={},p={},l={}{}${}", |
| 666 | state.variant, |
| 667 | state.version, |
| 668 | state.mem_cost, |
| 669 | state.time_cost, |
| 670 | state.lanes, |
| 671 | state.hash_length, |
| 672 | state.encode_ad(), |
| 673 | base64::encode_config(&state.salt, base64::STANDARD_NO_PAD), |
| 674 | )) |
| 675 | }, |
| 676 | } |
| 677 | } |
| 678 | |
| 679 | fn decode_from_string(&mut self, s: &str) -> Outcome<()> { |
| 680 | match self { |
| 681 | Self::Argon2(state) => state.from_argon2_string(s, true), |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | fn decode_cfg_from_string(&mut self, s: &str) -> Outcome<()> { |
| 686 | match self { |
| 687 | Self::Argon2(state) => state.from_argon2_string(s, false), |
| 688 | } |
| 689 | } |
| 690 | } |
| 691 | |
| 692 | #[derive(Clone, Debug, Default)] |
| 693 | pub struct KeyDeriverDefAlt< |
| 694 | D: KeyDeriver, |
| 695 | G: KeyDeriver, |
| 696 | > (pub DefAlt<D, G>); |
| 697 | |
| 698 | impl< |
| 699 | D: KeyDeriver, |
| 700 | G: KeyDeriver, |
| 701 | > |
| 702 | std::ops::Deref for KeyDeriverDefAlt<D, G> |
| 703 | { |
| 704 | type Target = DefAlt<D, G>; |
| 705 | fn deref(&self) -> &Self::Target { &self.0 } |
| 706 | } |
| 707 | |
| 708 | impl< |
| 709 | D: KeyDeriver, |
| 710 | G: KeyDeriver, |
| 711 | > |
| 712 | From<Option<G>> for KeyDeriverDefAlt<D, G> |
| 713 | { |
| 714 | fn from(opt: Option<G>) -> Self { |
| 715 | Self( |
| 716 | DefAlt::from(opt), |
| 717 | ) |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | impl< |
| 722 | D: KeyDeriver, |
| 723 | G: KeyDeriver, |
| 724 | > |
| 725 | From<Alt<G>> for KeyDeriverDefAlt<D, G> |
| 726 | { |
| 727 | fn from(alt: Alt<G>) -> Self { |
| 728 | Self( |
| 729 | DefAlt::from(alt), |
| 730 | ) |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | impl< |
| 735 | D: KeyDeriver + InNamex, |
| 736 | G: KeyDeriver + InNamex, |
| 737 | > |
| 738 | fmt::Display for KeyDeriverDefAlt<D, G> |
| 739 | { |
| 740 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 741 | write!(f, "{:?}", self) |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | impl< |
| 746 | D: KeyDeriver + InNamex, |
| 747 | G: KeyDeriver + InNamex, |
| 748 | > |
| 749 | InNamex for KeyDeriverDefAlt<D, G> |
| 750 | { |
| 751 | fn name_id(&self) -> Outcome<NamexId> { |
| 752 | match &self.0 { |
| 753 | DefAlt::Default(inner) => inner.name_id(), |
| 754 | DefAlt::Given(inner) => inner.name_id(), |
| 755 | DefAlt::None => Err(err!( |
| 756 | "No Namex id can be specified for DefAlt::None."; |
| 757 | Missing, Bug)), |
| 758 | } |
| 759 | } |
| 760 | |
| 761 | fn local_id(&self) -> LocalId { |
| 762 | match &self.0 { |
| 763 | DefAlt::Default(inner) => inner.local_id(), |
| 764 | DefAlt::Given(inner) => inner.local_id(), |
| 765 | DefAlt::None => LocalId::default(), |
| 766 | } |
| 767 | } |
| 768 | |
| 769 | fn assoc_names_base64( |
| 770 | gname: &'static str, |
| 771 | ) |
| 772 | -> Outcome<Option<Vec<( |
| 773 | &'static str, |
| 774 | &'static str, |
| 775 | )>>> |
| 776 | { |
| 777 | match res!(D::assoc_names_base64(gname)) { |
| 778 | Some(mut vd) => match res!(G::assoc_names_base64(gname)) { |
| 779 | Some(vg) => { |
| 780 | vd.extend(vg); |
| 781 | Ok(Some(vd)) |
| 782 | }, |
| 783 | None => Ok(Some(vd)), |
| 784 | }, |
| 785 | None => match res!(G::assoc_names_base64(gname)) { |
| 786 | Some(vg) => Ok(Some(vg)), |
| 787 | None => Ok(None), |
| 788 | }, |
| 789 | } |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | impl< |
| 794 | D: KeyDeriver, |
| 795 | G: KeyDeriver, |
| 796 | > |
| 797 | KeyDeriver for KeyDeriverDefAlt<D, G> |
| 798 | { |
| 799 | fn get_hash(&self) -> Outcome<&[u8]> { |
| 800 | match &self.0 { |
| 801 | DefAlt::Default(inner) => return inner.get_hash(), |
| 802 | DefAlt::Given(inner) => return inner.get_hash(), |
| 803 | DefAlt::None => (), |
| 804 | } |
| 805 | Err(err!( |
| 806 | "{}: Expected hash to be be present, found none.", self; |
| 807 | Data, Missing)) |
| 808 | } |
| 809 | |
| 810 | fn set_rand_salt(&mut self, n: usize) -> Outcome<()> { |
| 811 | match &mut self.0 { |
| 812 | DefAlt::Default(inner) => inner.set_rand_salt(n), |
| 813 | DefAlt::Given(inner) => inner.set_rand_salt(n), |
| 814 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 815 | Configuration, Missing)), |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | fn derive(&mut self, pass: &[u8]) -> Outcome<()> { |
| 820 | match &mut self.0 { |
| 821 | DefAlt::Default(inner) => inner.derive(pass), |
| 822 | DefAlt::Given(inner) => inner.derive(pass), |
| 823 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 824 | Configuration, Missing)), |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | fn verify(&self, pass: &[u8]) -> Outcome<bool> { |
| 829 | match &self.0 { |
| 830 | DefAlt::Default(inner) => inner.verify(pass), |
| 831 | DefAlt::Given(inner) => inner.verify(pass), |
| 832 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 833 | Configuration, Missing)), |
| 834 | } |
| 835 | } |
| 836 | |
| 837 | fn encode_to_string(&self) -> Outcome<String> { |
| 838 | match &self.0 { |
| 839 | DefAlt::Default(inner) => inner.encode_to_string(), |
| 840 | DefAlt::Given(inner) => inner.encode_to_string(), |
| 841 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 842 | Configuration, Missing)), |
| 843 | } |
| 844 | } |
| 845 | |
| 846 | fn encode_cfg_to_string(&self) -> Outcome<String> { |
| 847 | match &self.0 { |
| 848 | DefAlt::Default(inner) => inner.encode_cfg_to_string(), |
| 849 | DefAlt::Given(inner) => inner.encode_cfg_to_string(), |
| 850 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 851 | Configuration, Missing)), |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | fn decode_from_string(&mut self, s: &str) -> Outcome<()> { |
| 856 | match &mut self.0 { |
| 857 | DefAlt::Default(inner) => inner.decode_from_string(s), |
| 858 | DefAlt::Given(inner) => inner.decode_from_string(s), |
| 859 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 860 | Configuration, Missing)), |
| 861 | } |
| 862 | } |
| 863 | |
| 864 | fn decode_cfg_from_string(&mut self, s: &str) -> Outcome<()> { |
| 865 | match &mut self.0 { |
| 866 | DefAlt::Default(inner) => inner.decode_cfg_from_string(s), |
| 867 | DefAlt::Given(inner) => inner.decode_cfg_from_string(s), |
| 868 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 869 | Configuration, Missing)), |
| 870 | } |
| 871 | } |
| 872 | } |
| 873 | |
| 874 | impl< |
| 875 | D: KeyDeriver, |
| 876 | G: KeyDeriver, |
| 877 | > |
| 878 | KeyDeriverDefAlt<D, G> |
| 879 | { |
| 880 | pub const KDF_MISSING_MSG: &'static str = "Key deriver function not specified."; |
| 881 | |
| 882 | pub fn or_get_hash<'a, OR: KeyDeriver>(&'a self, alt: &'a Alt<OR>) -> Outcome<&'a [u8]> { |
| 883 | match &alt { |
| 884 | Alt::Specific(Some(inner)) => return inner.get_hash(), |
| 885 | Alt::Specific(None) => (), |
| 886 | Alt::Unspecified => match &self.0 { |
| 887 | DefAlt::Default(inner) => return inner.get_hash(), |
| 888 | DefAlt::Given(inner) => return inner.get_hash(), |
| 889 | DefAlt::None => (), |
| 890 | }, |
| 891 | } |
| 892 | Err(err!( |
| 893 | "{}: Expected hash to be be present, found none.", self; |
| 894 | Data, Missing)) |
| 895 | } |
| 896 | |
| 897 | pub fn or_set_rand_salt<OR: KeyDeriver>(&mut self, n: usize, mut alt: &mut Alt<OR>) -> Outcome<()> { |
| 898 | match &mut alt { |
| 899 | Alt::Specific(Some(inner)) => inner.set_rand_salt(n), |
| 900 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 901 | Configuration, Missing)), |
| 902 | Alt::Unspecified => match &mut self.0 { |
| 903 | DefAlt::Default(inner) => inner.set_rand_salt(n), |
| 904 | DefAlt::Given(inner) => inner.set_rand_salt(n), |
| 905 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 906 | Configuration, Missing)), |
| 907 | }, |
| 908 | } |
| 909 | } |
| 910 | |
| 911 | pub fn or_derive<OR: KeyDeriver>(&mut self, pass: &[u8], mut alt: &mut Alt<OR>) -> Outcome<()> { |
| 912 | match &mut alt { |
| 913 | Alt::Specific(Some(inner)) => inner.derive(pass), |
| 914 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 915 | Configuration, Missing)), |
| 916 | Alt::Unspecified => match &mut self.0 { |
| 917 | DefAlt::Default(inner) => inner.derive(pass), |
| 918 | DefAlt::Given(inner) => inner.derive(pass), |
| 919 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 920 | Configuration, Missing)), |
| 921 | }, |
| 922 | } |
| 923 | } |
| 924 | |
| 925 | pub fn or_verify<OR: KeyDeriver>(&self, pass: &[u8], alt: &Alt<OR>) -> Outcome<bool> { |
| 926 | match &alt { |
| 927 | Alt::Specific(Some(inner)) => inner.verify(pass), |
| 928 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 929 | Configuration, Missing)), |
| 930 | Alt::Unspecified => match &self.0 { |
| 931 | DefAlt::Default(inner) => inner.verify(pass), |
| 932 | DefAlt::Given(inner) => inner.verify(pass), |
| 933 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 934 | Configuration, Missing)), |
| 935 | }, |
| 936 | } |
| 937 | } |
| 938 | |
| 939 | pub fn or_encode_to_string<OR: KeyDeriver>(&self, alt: &Alt<OR>) -> Outcome<String> { |
| 940 | match &alt { |
| 941 | Alt::Specific(Some(inner)) => inner.encode_to_string(), |
| 942 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 943 | Configuration, Missing)), |
| 944 | Alt::Unspecified => match &self.0 { |
| 945 | DefAlt::Default(inner) => inner.encode_to_string(), |
| 946 | DefAlt::Given(inner) => inner.encode_to_string(), |
| 947 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 948 | Configuration, Missing)), |
| 949 | }, |
| 950 | } |
| 951 | } |
| 952 | |
| 953 | pub fn or_encode_cfg_to_string<OR: KeyDeriver>(&self, alt: &Alt<OR>) -> Outcome<String> { |
| 954 | match &alt { |
| 955 | Alt::Specific(Some(inner)) => inner.encode_cfg_to_string(), |
| 956 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 957 | Configuration, Missing)), |
| 958 | Alt::Unspecified => match &self.0 { |
| 959 | DefAlt::Default(inner) => inner.encode_cfg_to_string(), |
| 960 | DefAlt::Given(inner) => inner.encode_cfg_to_string(), |
| 961 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 962 | Configuration, Missing)), |
| 963 | }, |
| 964 | } |
| 965 | } |
| 966 | |
| 967 | pub fn or_decode_from_string<OR: KeyDeriver>(&mut self, s: &str, mut alt: &mut Alt<OR>) -> Outcome<()> { |
| 968 | match &mut alt { |
| 969 | Alt::Specific(Some(inner)) => inner.decode_from_string(s), |
| 970 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 971 | Configuration, Missing)), |
| 972 | Alt::Unspecified => match &mut self.0 { |
| 973 | DefAlt::Default(inner) => inner.decode_from_string(s), |
| 974 | DefAlt::Given(inner) => inner.decode_from_string(s), |
| 975 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 976 | Configuration, Missing)), |
| 977 | }, |
| 978 | } |
| 979 | } |
| 980 | |
| 981 | pub fn or_decode_cfg_from_string<OR: KeyDeriver>(&mut self, s: &str, mut alt: &mut Alt<OR>) -> Outcome<()> { |
| 982 | match &mut alt { |
| 983 | Alt::Specific(Some(inner)) => inner.decode_from_string(s), |
| 984 | Alt::Specific(None) => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 985 | Configuration, Missing)), |
| 986 | Alt::Unspecified => match &mut self.0 { |
| 987 | DefAlt::Default(inner) => inner.decode_from_string(s), |
| 988 | DefAlt::Given(inner) => inner.decode_from_string(s), |
| 989 | DefAlt::None => Err(err!("{}", Self::KDF_MISSING_MSG; |
| 990 | Configuration, Missing)), |
| 991 | }, |
| 992 | } |
| 993 | } |
| 994 | } |
| 995 | |
| 996 | #[cfg(test)] |
| 997 | mod tests { |
| 998 | use super::*; |
| 999 | use std::time::SystemTime; |
| 1000 | |
| 1001 | // The Argon2id test vector of RFC 9106, section 5.3. It is the only vector here that pins the |
| 1002 | // lane count, the secret and the associated data, because it is the only one that uses them. |
| 1003 | const RFC9106_PASS: [u8; 32] = [0x01; 32]; |
| 1004 | const RFC9106_SALT: [u8; 16] = [0x02; 16]; |
| 1005 | const RFC9106_SECRET: [u8; 8] = [0x03; 8]; |
| 1006 | const RFC9106_AD: [u8; 12] = [0x04; 12]; |
| 1007 | const RFC9106_TAG: [u8; 32] = [ |
| 1008 | 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, |
| 1009 | 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9, |
| 1010 | 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e, |
| 1011 | 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59, |
| 1012 | ]; |
| 1013 | |
| 1014 | /// The RFC 9106 section 5.3 inputs: 32 KiB of memory, 3 passes and, critically, 4 lanes. |
| 1015 | fn rfc9106_state() -> Argon2State { |
| 1016 | Argon2State { |
| 1017 | ad: RFC9106_AD.to_vec(), |
| 1018 | hash_length: 32, |
| 1019 | lanes: 4, |
| 1020 | mem_cost: 32, |
| 1021 | secret: RFC9106_SECRET.to_vec(), |
| 1022 | time_cost: 3, |
| 1023 | variant: argon2::Variant::Argon2id, |
| 1024 | version: argon2::Version::Version13, |
| 1025 | salt: RFC9106_SALT.to_vec(), |
| 1026 | hash: None, |
| 1027 | } |
| 1028 | } |
| 1029 | |
| 1030 | /// Pins our Argon2id output against the published RFC 9106 tag. |
| 1031 | #[test] |
| 1032 | fn test_argon2_rfc9106_vector() -> Outcome<()> { |
| 1033 | let mut kdf = KeyDerivationScheme::Argon2(rfc9106_state()); |
| 1034 | res!(kdf.derive(&RFC9106_PASS)); |
| 1035 | req!(res!(kdf.get_hash()), &RFC9106_TAG[..]); |
| 1036 | Ok(()) |
| 1037 | } |
| 1038 | |
| 1039 | /// Pins the configuration string round trip against the published RFC 9106 tag. Should the |
| 1040 | /// lane count or the tag length be dropped in encoding or decoding, the derived key differs |
| 1041 | /// from the vector and this fails. |
| 1042 | #[test] |
| 1043 | fn test_argon2_rfc9106_cfg_round_trip() -> Outcome<()> { |
| 1044 | let mut state = rfc9106_state(); |
| 1045 | state.secret = Vec::new(); // The secret is never encoded; it is supplied out of band. |
| 1046 | let kdf = KeyDerivationScheme::Argon2(state); |
| 1047 | let encoded_cfg = res!(kdf.encode_cfg_to_string()); |
| 1048 | msg!("encoded cfg = '{}'", encoded_cfg); |
| 1049 | // A fresh scheme carrying the defaults, lanes = 1 and hash_length = 32, which the decoded |
| 1050 | // configuration must overwrite. |
| 1051 | let mut kdf2 = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1052 | res!(kdf2.decode_cfg_from_string(&encoded_cfg)); |
| 1053 | match &mut kdf2 { |
| 1054 | KeyDerivationScheme::Argon2(state) => { |
| 1055 | req!(state.lanes, 4, "The lane count did not survive the round trip."); |
| 1056 | req!(state.hash_length, 32); |
| 1057 | req!(state.mem_cost, 32); |
| 1058 | req!(state.time_cost, 3); |
| 1059 | req!(state.version, argon2::Version::Version13); |
| 1060 | req!(&state.ad[..], &RFC9106_AD[..]); |
| 1061 | req!(&state.salt[..], &RFC9106_SALT[..]); |
| 1062 | state.secret = RFC9106_SECRET.to_vec(); // Supplied out of band, as documented. |
| 1063 | }, |
| 1064 | } |
| 1065 | res!(kdf2.derive(&RFC9106_PASS)); |
| 1066 | req!(res!(kdf2.get_hash()), &RFC9106_TAG[..]); |
| 1067 | Ok(()) |
| 1068 | } |
| 1069 | |
| 1070 | /// A foreign PHC string, produced elsewhere with 4 lanes, must verify against its password. |
| 1071 | /// Dropping the lanes on decoding derives a different tag, and the verification fails. |
| 1072 | #[test] |
| 1073 | fn test_argon2_decode_foreign_phc_string() -> Outcome<()> { |
| 1074 | let encoded = "$argon2i$v=19$m=4096,t=3,p=4$YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo\ |
| 1075 | $BvBk2OaSofBHfbrUW61nHrWB/43xgfs/QJJ5DkMAd8I"; |
| 1076 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2i", 0x13)); |
| 1077 | res!(kdf.decode_from_string(encoded)); |
| 1078 | match &kdf { |
| 1079 | KeyDerivationScheme::Argon2(state) => { |
| 1080 | req!(state.lanes, 4, "The lane count was dropped on decoding."); |
| 1081 | req!(state.mem_cost, 4096); |
| 1082 | req!(state.time_cost, 3); |
| 1083 | req!(state.hash_length, 32); |
| 1084 | req!(&state.salt[..], &b"abcdefghijklmnopqrstuvwxyz"[..]); |
| 1085 | }, |
| 1086 | } |
| 1087 | req!(res!(kdf.verify(b"foo")), true); |
| 1088 | req!(res!(kdf.verify(b"bar")), false); |
| 1089 | Ok(()) |
| 1090 | } |
| 1091 | |
| 1092 | /// Our PHC output must be readable by an independent Argon2 parser, here the wrapped crate's |
| 1093 | /// own, which we otherwise never exercise. |
| 1094 | #[test] |
| 1095 | fn test_argon2_phc_string_is_externally_readable() -> Outcome<()> { |
| 1096 | let pass = b"The meaning is 42"; |
| 1097 | let state = res!(Argon2State::new("Argon2id", 0x13, 1024, 2, 4, 32, b"somesalt".to_vec())); |
| 1098 | let mut kdf = KeyDerivationScheme::Argon2(state); |
| 1099 | res!(kdf.derive(pass)); |
| 1100 | let encoded = res!(kdf.encode_to_string()); |
| 1101 | msg!("encoded with hash = '{}'", encoded); |
| 1102 | req!(encoded.starts_with("$argon2id$v=19$m=1024,t=2,p=4$"), true, "Not a PHC string."); |
| 1103 | match argon2::verify_encoded(&encoded, pass) { |
| 1104 | Ok(true) => (), |
| 1105 | Ok(false) => return Err(err!( |
| 1106 | "An independent Argon2 parser read our encoded string but did not verify \ |
| 1107 | the password against it."; Test, Mismatch)), |
| 1108 | Err(e) => return Err(err!(e, |
| 1109 | "An independent Argon2 parser could not read our encoded string."; Test, Decode)), |
| 1110 | } |
| 1111 | Ok(()) |
| 1112 | } |
| 1113 | |
| 1114 | /// A configuration string written by the previous release must still decode, and must still |
| 1115 | /// derive the key it derived then. |
| 1116 | /// |
| 1117 | /// The deployed encoder wrote `$argon2id$v=19$m=..,t=..,p=..$<salt>` and no `l` option, because |
| 1118 | /// it never wrote the tag length at all. Every `kdf_cfg` in a wallet in the field has that |
| 1119 | /// shape, and a wallet is the one artefact that cannot be rebuilt from source: refusing to read |
| 1120 | /// it would lock every admin out of their own master key. So a missing `l` is not an error, it |
| 1121 | /// is the old format, and the tag length falls back to the one the state already carries. |
| 1122 | /// |
| 1123 | /// The expected tag comes from the wrapped crate's own hasher rather than from ours, so this |
| 1124 | /// pins the fallback to what the old string actually meant, not to what we now think it means. |
| 1125 | #[test] |
| 1126 | fn test_a_cfg_string_from_the_previous_release_still_decodes() -> Outcome<()> { |
| 1127 | let pass = b"The meaning is 42"; |
| 1128 | let salt = b"somesalt"; |
| 1129 | // Exactly what the previous release's `encode_cfg_to_string` emitted: no 'l' option. |
| 1130 | let old_cfg = fmt!( |
| 1131 | "$argon2id$v=19$m=1024,t=2,p=1${}", |
| 1132 | base64::encode_config(salt, base64::STANDARD_NO_PAD), |
| 1133 | ); |
| 1134 | |
| 1135 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1136 | res!(kdf.decode_cfg_from_string(&old_cfg)); |
| 1137 | res!(kdf.derive(pass)); |
| 1138 | let ours = res!(kdf.get_hash()).to_vec(); |
| 1139 | |
| 1140 | // The independent oracle: the wrapped crate, told the same parameters by hand. |
| 1141 | let cfg = argon2::Config { |
| 1142 | variant: argon2::Variant::Argon2id, |
| 1143 | version: argon2::Version::Version13, |
| 1144 | mem_cost: 1024, |
| 1145 | time_cost: 2, |
| 1146 | lanes: 1, |
| 1147 | hash_length: 32, |
| 1148 | ..argon2::Config::default() |
| 1149 | }; |
| 1150 | let theirs = res!(argon2::hash_raw(pass, salt, &cfg)); |
| 1151 | |
| 1152 | req!(ours, theirs, |
| 1153 | "A cfg string from the previous release derived a different key than it used to, \ |
| 1154 | which is how a wallet locks its owner out."); |
| 1155 | Ok(()) |
| 1156 | } |
| 1157 | |
| 1158 | #[test] |
| 1159 | fn test_argon2_encode_pass() -> Outcome<()> { |
| 1160 | // Here we store the hash along with the hasher config. |
| 1161 | let pass = b"The meaning is 42"; |
| 1162 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1163 | res!(kdf.derive(pass)); |
| 1164 | let encoded_cfg = res!(kdf.encode_cfg_to_string()); |
| 1165 | let encoded_with_hash = res!(kdf.encode_to_string()); |
| 1166 | let hash = res!(kdf.get_hash()); |
| 1167 | msg!("hash = '{:02x?}'", hash); |
| 1168 | msg!("encoded cfg = '{}'", encoded_cfg); |
| 1169 | msg!("encoded with hash = '{}'", encoded_with_hash); |
| 1170 | req!(res!(kdf.verify(pass)), true); |
| 1171 | req!(res!(kdf.verify(b"The meaning is 43")), false, |
| 1172 | "The verification should have failed for a differing passphrase."); |
| 1173 | let mut kdf2 = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1174 | res!(kdf2.decode_from_string(&encoded_with_hash)); |
| 1175 | req!(kdf, kdf2); |
| 1176 | let mut kdf3 = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1177 | res!(kdf3.decode_cfg_from_string(&encoded_cfg)); |
| 1178 | res!(kdf3.derive(pass)); |
| 1179 | req!(kdf, kdf3); |
| 1180 | Ok(()) |
| 1181 | } |
| 1182 | |
| 1183 | /// A configuration string carrying neither a hash nor an explicit tag length keeps the tag |
| 1184 | /// length the receiving state already holds. |
| 1185 | /// |
| 1186 | /// That is the old format, and it is what every deployed wallet contains, so it must decode. |
| 1187 | /// New strings do not rely on the fallback: `encode_cfg_to_string` always writes `l`, which is |
| 1188 | /// what makes them self-describing. See |
| 1189 | /// `test_a_cfg_string_from_the_previous_release_still_decodes` for the key it must derive. |
| 1190 | #[test] |
| 1191 | fn test_argon2_decode_without_a_tag_length_keeps_the_states_own() -> Outcome<()> { |
| 1192 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1193 | let expected = match &kdf { |
| 1194 | KeyDerivationScheme::Argon2(state) => state.hash_length, |
| 1195 | }; |
| 1196 | res!(kdf.decode_cfg_from_string("$argon2id$v=19$m=65536,t=5,p=1$c29tZXNhbHQ")); |
| 1197 | match &kdf { |
| 1198 | KeyDerivationScheme::Argon2(state) => { |
| 1199 | req!(state.hash_length, expected, |
| 1200 | "An old configuration string changed the tag length it derives with."); |
| 1201 | req!(state.lanes, 1); |
| 1202 | }, |
| 1203 | } |
| 1204 | Ok(()) |
| 1205 | } |
| 1206 | |
| 1207 | /// An option we do not understand may be one that changes the derived key, so it is rejected. |
| 1208 | #[test] |
| 1209 | fn test_argon2_decode_rejects_unknown_option() -> Outcome<()> { |
| 1210 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1211 | match kdf.decode_cfg_from_string("$argon2id$v=19$m=65536,t=5,p=1,l=32,x=9$c29tZXNhbHQ") { |
| 1212 | Ok(()) => Err(err!( |
| 1213 | "A configuration string with an unknown option should not decode."; |
| 1214 | Test, Unexpected)), |
| 1215 | Err(_) => Ok(()), |
| 1216 | } |
| 1217 | } |
| 1218 | |
| 1219 | /// The lane count is mandatory, not optional. |
| 1220 | #[test] |
| 1221 | fn test_argon2_decode_rejects_missing_lanes() -> Outcome<()> { |
| 1222 | let mut kdf = res!(KeyDerivationScheme::default_argon2("Argon2id", 0x13)); |
| 1223 | match kdf.decode_cfg_from_string("$argon2id$v=19$m=65536,t=5,l=32$c29tZXNhbHQ") { |
| 1224 | Ok(()) => Err(err!( |
| 1225 | "A configuration string with no lane count should not decode."; Test, Unexpected)), |
| 1226 | Err(_) => Ok(()), |
| 1227 | } |
| 1228 | } |
| 1229 | |
| 1230 | /// A secret is never written to an encoded string, so encoding one must fail loudly rather |
| 1231 | /// than drop it, which would derive a different key on decoding. |
| 1232 | #[test] |
| 1233 | fn test_argon2_encode_refuses_to_drop_secret() -> Outcome<()> { |
| 1234 | let kdf = KeyDerivationScheme::Argon2(rfc9106_state()); |
| 1235 | match kdf.encode_cfg_to_string() { |
| 1236 | Ok(s) => Err(err!( |
| 1237 | "Encoding a state holding a secret should have failed, but produced '{}'.", s; |
| 1238 | Test, Unexpected)), |
| 1239 | Err(_) => Ok(()), |
| 1240 | } |
| 1241 | } |
| 1242 | |
| 1243 | /// A simple test of viability for a network packet header proof of work. |
| 1244 | #[test] |
| 1245 | fn test_argon2_pow() -> Outcome<()> { |
| 1246 | // Deliberately cheap parameters: this measures viability, not key strength. |
| 1247 | let mut kdf = res!(KeyDerivationScheme::new_argon2("Argon2i", 0x13, 512, 1, 16, 32)); |
| 1248 | let prefix = [1u8]; // One byte, so around 256 derivations are expected. |
| 1249 | let lim: usize = 20_000; |
| 1250 | let mut count: usize = 0; |
| 1251 | let t = res!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)); |
| 1252 | let mut tstamp = t.as_secs().to_be_bytes().to_vec(); |
| 1253 | let mut pass = vec![192u8, 168, 0, 1, 127, 0, 0, 1]; |
| 1254 | pass.append(&mut tstamp); |
| 1255 | loop { |
| 1256 | res!(kdf.derive(&pass)); |
| 1257 | let hash = res!(kdf.get_hash()); |
| 1258 | if hash.starts_with(&prefix) { break; } |
| 1259 | count += 1; |
| 1260 | if count > lim { |
| 1261 | return Err(err!( |
| 1262 | "No proof of work found for a {} byte prefix in {} derivations.", |
| 1263 | prefix.len(), lim; |
| 1264 | Test, Excessive)); |
| 1265 | } |
| 1266 | res!(kdf.set_rand_salt(16)); |
| 1267 | } |
| 1268 | msg!("Proof of work found after {} failed derivations.", count); |
| 1269 | Ok(()) |
| 1270 | } |
| 1271 | |
| 1272 | } |