AkurAI Build
Menu

Public repository / master

AkurAI-Framework / crates/crypto/tests/helpers.rs

Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson

//! Hex, constant-time compare, randomness and password hashing.

use akurai_crypto::{ct_eq, from_hex, hex, verify_password};

#[test]
fn hex_is_lowercase_and_round_trips() {
    let bytes = [0x00, 0x01, 0xab, 0xff, 0x7f];
    assert_eq!(hex(&bytes), "0001abff7f");
    assert_eq!(from_hex("0001abff7f"), Some(bytes.to_vec()));
    assert_eq!(from_hex("0001ABFF7F"), Some(bytes.to_vec()));
    assert_eq!(hex(&[]), "");
    assert_eq!(from_hex(""), Some(vec![]));
}

#[test]
fn from_hex_rejects_bad_input() {
    assert_eq!(from_hex("abc"), None, "odd length");
    assert_eq!(from_hex("zz"), None, "non-hex digit");
    assert_eq!(from_hex("0g"), None, "non-hex digit");
    assert_eq!(from_hex("é1"), None, "non-ascii");
    assert_eq!(from_hex("+1"), None, "sign is not a digit");
}

#[test]
fn ct_eq_compares_content_and_length() {
    assert!(ct_eq(b"", b""));
    assert!(ct_eq(b"secret", b"secret"));
    assert!(!ct_eq(b"secret", b"secreT"));
    assert!(!ct_eq(b"secret", b"secret!"));
    assert!(!ct_eq(b"a", b""));
}

#[test]
fn verify_password_rejects_malformed_strings() {
    let good_salt = "00112233445566778899aabbccddeeff";
    let good_hash = "00".repeat(32);
    for bad in [
        String::new(),
        "garbage".to_string(),
        format!("pbkdf2-sha256$1${good_salt}"),
        format!("pbkdf2-sha256$1${good_salt}${good_hash}$extra"),
        format!("bcrypt$1${good_salt}${good_hash}"),
        format!("pbkdf2-sha256$0${good_salt}${good_hash}"),
        format!("pbkdf2-sha256$-1${good_salt}${good_hash}"),
        format!("pbkdf2-sha256$abc${good_salt}${good_hash}"),
        format!("pbkdf2-sha256$1$zz${good_hash}"),
        format!("pbkdf2-sha256$1${good_salt}$"),
        format!("pbkdf2-sha256$1${good_salt}$abc"),
    ] {
        assert!(!verify_password("pw", &bad), "accepted {bad:?}");
    }
}

#[test]
fn verify_password_accepts_hand_built_low_iteration_hash() {
    let salt = b"0123456789abcdef";
    let mut out = [0u8; 32];
    akurai_crypto::pbkdf2_sha256(b"hunter2", salt, 2, &mut out);
    let encoded = format!("pbkdf2-sha256$2${}${}", hex(salt), hex(&out));
    assert!(verify_password("hunter2", &encoded));
    assert!(!verify_password("hunter3", &encoded));
}

#[cfg(not(target_arch = "wasm32"))]
mod os {
    use akurai_crypto::{
        from_hex, hash_password, random_bytes, random_token, verify_password, PASSWORD_ITERATIONS,
    };

    #[test]
    fn random_bytes_fills_buffer() {
        let mut a = [0u8; 32];
        let mut b = [0u8; 32];
        random_bytes(&mut a).unwrap();
        random_bytes(&mut b).unwrap();
        assert_ne!(a, b);
        assert_ne!(a, [0u8; 32]);
    }

    #[test]
    fn random_token_is_hex_of_requested_length() {
        let t = random_token(24).unwrap();
        assert_eq!(t.len(), 48);
        assert_eq!(from_hex(&t).map(|v| v.len()), Some(24));
        assert_ne!(t, random_token(24).unwrap());
    }

    #[test]
    fn hash_password_format_and_round_trip() {
        let encoded = hash_password("correct horse").unwrap();
        let parts: Vec<&str> = encoded.split('$').collect();
        assert_eq!(parts.len(), 4);
        assert_eq!(parts[0], "pbkdf2-sha256");
        assert_eq!(parts[1], PASSWORD_ITERATIONS.to_string());
        assert_eq!(from_hex(parts[2]).unwrap().len(), 16);
        assert_eq!(from_hex(parts[3]).unwrap().len(), 32);
        assert!(verify_password("correct horse", &encoded));
        assert!(!verify_password("correct horsf", &encoded));
        assert!(!verify_password("", &encoded));
    }

    #[test]
    fn hashing_same_password_twice_uses_fresh_salt() {
        let a = hash_password("same").unwrap();
        let b = hash_password("same").unwrap();
        assert_ne!(a, b);
        assert!(verify_password("same", &a));
        assert!(verify_password("same", &b));
    }
}