1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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(())
    }
}