vncrs 0.1.8

A pure Rust VNC server library for Windows
use crate::error::{Result, VncError};
use cipher::{BlockEncrypt, KeyInit};
use des::Des;
use rand::RngCore;
use std::io::{Read, Write};

use log::{info, warn};

#[inline]
pub(crate) fn reverse_bits(byte: u8) -> u8 {
    byte.reverse_bits()
}

fn password_to_key(password: &str) -> [u8; 8] {
    let mut key = [0u8; 8];
    for (i, &b) in password.as_bytes().iter().take(8).enumerate() {
        key[i] = reverse_bits(b);
    }
    key
}

fn encrypt_challenge(key: &[u8; 8], challenge: &[u8; 16]) -> [u8; 16] {
    let cipher = Des::new_from_slice(key).unwrap();
    let mut result = [0u8; 16];
    result.copy_from_slice(challenge);

    let (block1, block2) = result.split_at_mut(8);
    cipher.encrypt_block(block1.into());
    cipher.encrypt_block(block2.into());

    result
}

/// Constant-time slice comparison to prevent timing side-channels
#[inline]
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }
    let mut diff = 0u8;
    for (&x, &y) in a.iter().zip(b.iter()) {
        diff |= x ^ y;
    }
    diff == 0
}

pub fn perform_vnc_auth<R: Read, W: Write>(
    reader: &mut R,
    writer: &mut W,
    password: &str,
) -> Result<()> {
    let mut challenge = [0u8; 16];
    rand::thread_rng().fill_bytes(&mut challenge);

    writer.write_all(&challenge)?;
    writer.flush()?;

    let mut client_response = [0u8; 16];
    reader.read_exact(&mut client_response)?;

    let key = password_to_key(password);
    let expected = encrypt_challenge(&key, &challenge);

    if constant_time_eq(&client_response, &expected) {
        writer.write_all(&0u32.to_be_bytes())?;
        writer.flush()?;
        info!("Authentication successful");
        Ok(())
    } else {
        writer.write_all(&1u32.to_be_bytes())?;
        let reason = b"Authentication failed";
        writer.write_all(&(reason.len() as u32).to_be_bytes())?;
        writer.write_all(reason)?;
        writer.flush()?;
        warn!("Authentication FAILED");
        Err(VncError::AuthFailed)
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_reverse_bits() {
        assert_eq!(reverse_bits(0b10000000), 0b00000001);
        assert_eq!(reverse_bits(0b11001010), 0b01010011);
        assert_eq!(reverse_bits(0xFF), 0xFF);
        assert_eq!(reverse_bits(0x00), 0x00);
    }

    #[test]
    fn test_password_to_key_length() {
        let key = password_to_key("test");
        assert_eq!(key.len(), 8);
    }

    #[test]
    fn test_password_to_key_truncation() {
        let key_short = password_to_key("ab");
        let key_long = password_to_key("abcdefghijklmnop");
        assert_eq!(key_short[0], key_long[0]);
        assert_eq!(key_short[1], key_long[1]);
        assert_eq!(key_short[2], 0);
    }

    #[test]
    fn test_encrypt_challenge_deterministic() {
        let key = password_to_key("secret");
        let challenge = [1u8; 16];
        let result1 = encrypt_challenge(&key, &challenge);
        let result2 = encrypt_challenge(&key, &challenge);
        assert_eq!(result1, result2);
    }

    #[test]
    fn test_auth_success() {
        let password = "secret";
        let key = password_to_key(password);
        let challenge = [42u8; 16];

        let client_response = encrypt_challenge(&key, &challenge);
        let expected = encrypt_challenge(&key, &challenge);
        assert_eq!(client_response, expected);
    }

    #[test]
    fn test_auth_wrong_password() {
        let server_key = password_to_key("correct");
        let client_key = password_to_key("wrong");
        let challenge = [7u8; 16];

        let server_expected = encrypt_challenge(&server_key, &challenge);
        let client_response = encrypt_challenge(&client_key, &challenge);
        assert_ne!(server_expected, client_response);
    }
}