use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use pakery_core::crypto::Ksf;
use pakery_core::PakeError;
use zeroize::Zeroizing;
const ARGON2_KSF_SALT: &[u8] = b"OPAQUE-Argon2id";
pub trait Argon2Params {
const M_COST: u32;
const T_COST: u32;
const P_COST: u32;
const OUTPUT_LEN: usize;
}
pub struct DefaultArgon2Params;
impl Argon2Params for DefaultArgon2Params {
const M_COST: u32 = 65536;
const T_COST: u32 = 3;
const P_COST: u32 = 4;
const OUTPUT_LEN: usize = 64;
}
pub struct Argon2idKsfWithParams<P: Argon2Params>(PhantomData<P>);
impl<P: Argon2Params> Ksf for Argon2idKsfWithParams<P> {
fn stretch(input: &[u8]) -> Result<Zeroizing<Vec<u8>>, PakeError> {
use argon2::{Algorithm, Argon2, Params, Version};
let params = Params::new(P::M_COST, P::T_COST, P::P_COST, Some(P::OUTPUT_LEN))
.map_err(|_| PakeError::ProtocolError("argon2 params"))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut output = vec![0u8; P::OUTPUT_LEN];
argon2
.hash_password_into(input, ARGON2_KSF_SALT, &mut output)
.map_err(|_| PakeError::ProtocolError("argon2 hash"))?;
Ok(Zeroizing::new(output))
}
}
pub type Argon2idKsf = Argon2idKsfWithParams<DefaultArgon2Params>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_alias_matches_v0_1_config() {
assert_eq!(<DefaultArgon2Params as Argon2Params>::M_COST, 65536);
assert_eq!(<DefaultArgon2Params as Argon2Params>::T_COST, 3);
assert_eq!(<DefaultArgon2Params as Argon2Params>::P_COST, 4);
assert_eq!(<DefaultArgon2Params as Argon2Params>::OUTPUT_LEN, 64);
assert_eq!(ARGON2_KSF_SALT, b"OPAQUE-Argon2id");
let out = Argon2idKsf::stretch(b"correct horse battery staple").unwrap();
const EXPECTED: [u8; 64] = [
0x7d, 0x7d, 0xbc, 0x32, 0x79, 0xd7, 0xca, 0xac, 0xd7, 0x7f, 0x4f, 0x94, 0x07, 0x17,
0xc1, 0x17, 0x4f, 0x4f, 0x03, 0x68, 0xab, 0x23, 0x8c, 0xb2, 0xf7, 0xef, 0xab, 0x6f,
0xf0, 0x22, 0x52, 0x5d, 0x8d, 0x76, 0x8e, 0xc5, 0xcd, 0x86, 0xe8, 0x52, 0x99, 0xe1,
0x8e, 0x42, 0x18, 0x29, 0x09, 0x21, 0xd1, 0x25, 0xfe, 0x8e, 0x4d, 0xb7, 0x19, 0xf3,
0x45, 0x6c, 0x80, 0xd9, 0xef, 0xe5, 0x9b, 0x3e,
];
assert_eq!(
out.as_slice(),
EXPECTED.as_slice(),
"Argon2idKsf stretch must match pinned v0.1.x output"
);
}
#[test]
fn custom_params_differ_from_default() {
struct FastParams;
impl Argon2Params for FastParams {
const M_COST: u32 = 8;
const T_COST: u32 = 1;
const P_COST: u32 = 1;
const OUTPUT_LEN: usize = 64;
}
let default_out = Argon2idKsf::stretch(b"hunter2").unwrap();
let fast_out = Argon2idKsfWithParams::<FastParams>::stretch(b"hunter2").unwrap();
assert_ne!(default_out.as_slice(), fast_out.as_slice());
assert_eq!(default_out.len(), 64);
assert_eq!(fast_out.len(), 64);
}
#[test]
fn output_len_controls_result_length() {
struct Out32;
impl Argon2Params for Out32 {
const M_COST: u32 = 8;
const T_COST: u32 = 1;
const P_COST: u32 = 1;
const OUTPUT_LEN: usize = 32;
}
struct Out64;
impl Argon2Params for Out64 {
const M_COST: u32 = 8;
const T_COST: u32 = 1;
const P_COST: u32 = 1;
const OUTPUT_LEN: usize = 64;
}
let out32 = Argon2idKsfWithParams::<Out32>::stretch(b"x").unwrap();
let out64 = Argon2idKsfWithParams::<Out64>::stretch(b"x").unwrap();
assert_eq!(out32.len(), 32);
assert_eq!(out64.len(), 64);
}
}