use altcha::{
create_challenge, solve_challenge, verify_solution, CreateChallengeOptions, Solution,
SolveChallengeOptions, VerifySolutionOptions,
};
fn secret() -> String {
"test-secret".to_string()
}
fn roundtrip(algorithm: &str, cost: u32) {
let options = CreateChallengeOptions {
algorithm: algorithm.to_string(),
cost,
hmac_signature_secret: Some(secret()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
assert!(challenge.signature.is_some(), "challenge must be signed");
let solution = solve_challenge(SolveChallengeOptions::new(&challenge))
.expect("solve_challenge failed")
.expect("no solution found within timeout");
let result = verify_solution(VerifySolutionOptions::new(&challenge, &solution, secret()))
.expect("verify_solution failed");
assert!(result.verified, "solution must verify for {algorithm}");
assert!(!result.expired);
assert_eq!(result.invalid_signature, Some(false));
assert_eq!(result.invalid_solution, Some(false));
}
#[test]
fn roundtrip_pbkdf2_sha256() {
roundtrip("PBKDF2/SHA-256", 100);
}
#[test]
fn roundtrip_pbkdf2_sha384() {
roundtrip("PBKDF2/SHA-384", 100);
}
#[test]
fn roundtrip_pbkdf2_sha512() {
roundtrip("PBKDF2/SHA-512", 100);
}
#[test]
fn roundtrip_sha256() {
roundtrip("SHA-256", 10);
}
#[test]
fn roundtrip_sha384() {
roundtrip("SHA-384", 10);
}
#[test]
fn roundtrip_sha512() {
roundtrip("SHA-512", 10);
}
#[cfg(feature = "scrypt")]
#[test]
fn roundtrip_scrypt() {
roundtrip("SCRYPT", 1024); }
#[cfg(feature = "argon2")]
#[test]
fn roundtrip_argon2id() {
let options = CreateChallengeOptions {
algorithm: "ARGON2ID".to_string(),
cost: 1, memory_cost: Some(8), parallelism: Some(1),
key_prefix: "00".to_string(),
hmac_signature_secret: Some(secret()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
let solution = solve_challenge(SolveChallengeOptions::new(&challenge))
.expect("solve_challenge failed")
.expect("no solution found");
let result = verify_solution(VerifySolutionOptions::new(&challenge, &solution, secret()))
.expect("verify_solution failed");
assert!(result.verified, "argon2id solution must verify");
}
#[test]
fn deterministic_mode_roundtrip() {
let options = CreateChallengeOptions {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 100,
counter: Some(0),
hmac_signature_secret: Some(secret()),
hmac_key_signature_secret: Some("key-secret".to_string()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
assert!(
challenge.parameters.key_signature.is_some(),
"keySignature must be present in deterministic mode"
);
let solution = solve_challenge(SolveChallengeOptions::new(&challenge))
.expect("solve_challenge failed")
.expect("no solution found");
let result = verify_solution(VerifySolutionOptions {
hmac_key_signature_secret: Some("key-secret".to_string()),
..VerifySolutionOptions::new(&challenge, &solution, secret())
})
.expect("verify_solution failed");
assert!(result.verified, "deterministic mode must verify");
assert_eq!(result.invalid_solution, Some(false));
}
#[test]
fn wrong_secret_fails_verification() {
let options = CreateChallengeOptions {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 100,
hmac_signature_secret: Some(secret()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
let solution = solve_challenge(SolveChallengeOptions::new(&challenge))
.expect("solve_challenge failed")
.expect("no solution found");
let result =
verify_solution(VerifySolutionOptions::new(&challenge, &solution, "wrong-secret"))
.expect("verify_solution failed");
assert!(!result.verified);
assert_eq!(result.invalid_signature, Some(true));
}
#[test]
fn tampered_counter_fails_verification() {
let options = CreateChallengeOptions {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 100,
hmac_signature_secret: Some(secret()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
let mut solution = solve_challenge(SolveChallengeOptions::new(&challenge))
.expect("solve_challenge failed")
.expect("no solution found");
solution.counter = solution.counter.wrapping_add(1);
let result = verify_solution(VerifySolutionOptions::new(&challenge, &solution, secret()))
.expect("verify_solution failed");
assert!(!result.verified);
assert_eq!(result.invalid_solution, Some(true));
}
#[test]
fn unsigned_challenge_fails_verification() {
let options = CreateChallengeOptions {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 100,
hmac_signature_secret: None, ..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
let solution = Solution {
counter: 0,
derived_key: "00".to_string(),
time: None,
};
let result = verify_solution(VerifySolutionOptions::new(&challenge, &solution, secret()))
.expect("verify_solution failed");
assert!(!result.verified);
assert_eq!(result.invalid_signature, Some(true));
}
#[test]
fn expired_challenge_fails_verification() {
let past = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
- 1;
let options = CreateChallengeOptions {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 100,
expires_at: Some(past),
hmac_signature_secret: Some(secret()),
..Default::default()
};
let challenge = create_challenge(options).expect("create_challenge failed");
let solution = Solution {
counter: 0,
derived_key: "00".to_string(),
time: None,
};
let result = verify_solution(VerifySolutionOptions::new(&challenge, &solution, secret()))
.expect("verify_solution failed");
assert!(!result.verified);
assert!(result.expired);
}
#[test]
fn canonical_json_sorted_keys() {
use altcha::ChallengeParameters;
use serde_json;
let params = ChallengeParameters {
algorithm: "PBKDF2/SHA-256".to_string(),
cost: 1000,
data: None,
expires_at: None,
key_length: 32,
key_prefix: "00".to_string(),
key_signature: None,
memory_cost: None,
nonce: "aabbccdd".to_string(),
parallelism: None,
salt: "eeff0011".to_string(),
};
let json = serde_json::to_string(¶ms).unwrap();
let expected_keys = ["algorithm", "cost", "keyLength", "keyPrefix", "nonce", "salt"];
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let obj = value.as_object().unwrap();
let actual_keys: Vec<&str> = obj.keys().map(|s| s.as_str()).collect();
assert_eq!(actual_keys, expected_keys, "JSON keys must be sorted alphabetically");
}