lite-session 1.0.0

Create Session Tokens that are Resilient to Misuse and Highjacking
Documentation
use crate::{LiteSessionData, LiteSessionError, SessionTokenRng};
use chacha20::{
    cipher::{NewStreamCipher, StreamCipher, SyncStreamCipher, SyncStreamCipherSeek},
    ChaCha8, Key, Nonce,
};
use core::fmt::Debug;

/// Holds the generated `ChaCha8` cipher text and r`andom generated nonce`
#[derive(Debug)]
pub struct CipherText {
    pub(crate) cipher: CipherHex,
    pub(crate) nonce: String,
}

type CipherHex = String;

impl Default for CipherText {
    fn default() -> Self {
        Self {
            cipher: CipherHex::default(),
            nonce: String::default(),
        }
    }
}

impl CipherText {
    /// Encrypts the `user data` of the token to prevent eavesdropping of its contents
    pub fn encrypt(
        &mut self,
        ls_data: &LiteSessionData,
        key: &[u8],
    ) -> Result<&Self, LiteSessionError> {
        if key.len() != 32 {
            return Err(LiteSessionError::ServerKeyLengthError);
        }

        let nonce_string = SessionTokenRng::nonce();

        let key = Key::from_slice(key);
        let nonce = Nonce::from_slice(&nonce_string.as_bytes());

        let mut cipher = ChaCha8::new(&key, &nonce);
        let mut cipher_text = ls_data.build().into_bytes();
        cipher.apply_keystream(&mut cipher_text);

        let cipher_hex = hex::encode(cipher_text);

        self.cipher = cipher_hex;
        self.nonce = nonce_string;

        Ok(self)
    }

    /// Decrypts the user data
    pub fn decrypt(
        &self,
        key: &[u8],
        mut ciphertext: &mut [u8],
        nonce: &[u8],
    ) -> Result<LiteSessionData, LiteSessionError> {
        if key.len() != 32 {
            return Err(LiteSessionError::ServerKeyLengthError);
        }

        if nonce.len() != 12 {
            return Err(LiteSessionError::NonceLengthError);
        }

        let key = Key::from_slice(key);
        let nonce = Nonce::from_slice(nonce);
        let mut cipher = ChaCha8::new(&key, &nonce);
        cipher.seek(0);
        cipher.decrypt(&mut ciphertext);

        let raw_data = match String::from_utf8(ciphertext.to_vec()) {
            Ok(data) => data,
            Err(_) => return Err(LiteSessionError::FromUtf8TokenError),
        };

        LiteSessionData::default().destructure(&raw_data)
    }
}

#[cfg(test)]
mod ciphertext_tests {
    use super::CipherText;
    use crate::{LiteSessionData, LiteSessionError, Role};

    #[test]
    fn cipher() -> Result<(), LiteSessionError> {
        let mut data = LiteSessionData::default();

        data.username("foo_user");
        data.role(Role::SuperUser);
        data.tag("Foo-Tag");
        data.add_acl("Network-TCP");
        data.add_acl("Network-UDP");

        let bad_key = [0_u8; 32];
        let bad_key2 = [1_u8; 32];

        let mut ciphertext = CipherText::default();
        ciphertext.encrypt(&data, &bad_key)?;

        let decrypt_ops = CipherText::default();
        let mut ciphertext_bytes = match hex::decode(ciphertext.cipher) {
            Ok(bytes) => bytes,
            Err(_) => return Err(LiteSessionError::InvalidHexString),
        };

        let decryption = decrypt_ops.decrypt(
            &bad_key,
            &mut ciphertext_bytes,
            &ciphertext.nonce.as_bytes(),
        )?;
        let bad_decryption = decrypt_ops.decrypt(
            &bad_key2,
            &mut ciphertext_bytes,
            &ciphertext.nonce.as_bytes(),
        );

        assert_eq!(data, decryption);

        assert_eq!(bad_decryption, Err(LiteSessionError::FromUtf8TokenError));

        Ok(())
    }
}