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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use byteorder::{LittleEndian, WriteBytesExt};
use chacha20poly1305::{
    aead::{rand_core::RngCore, OsRng},
    AeadInPlace, ChaCha20Poly1305, KeyInit, Tag, XChaCha20Poly1305, XNonce,
};
use std::io;

use crate::{MAC_BYTES, PRIVATE_KEY_BYTES};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("buffer size mismatch")]
    BufferSizeMismatch,
    #[error("failed to encrypt: {0}")]
    Failed(#[from] chacha20poly1305::aead::Error),
    #[error("failed to generate key: {0}")]
    GenerateKey(chacha20poly1305::aead::rand_core::Error),
}
/// A 32-byte array, used as a key for encrypting and decrypting packets and connect tokens.
pub type Key = [u8; crate::PRIVATE_KEY_BYTES];
pub type Result<T> = std::result::Result<T, Error>;

/// Generates a random key for encrypting and decrypting packets and connect tokens.
///
/// Panics if the underlying RNG fails (highly unlikely). <br>
/// For a non-panicking version, see [`try_generate_key`](fn.try_generate_key.html).
///
/// # Example
/// ```
/// use netcode::generate_key;
///
/// let key = generate_key();
/// assert_eq!(key.len(), 32);
/// ```
pub fn generate_key() -> Key {
    let mut key: Key = [0; PRIVATE_KEY_BYTES];
    OsRng.fill_bytes(&mut key);
    key
}
/// The fallible version of [`generate_key`](fn.generate_key.html).
///
/// Returns an error if the underlying RNG fails (highly unlikely).
///
/// # Example
/// ```
/// use netcode::try_generate_key;
///
/// let key = try_generate_key().unwrap();
/// assert_eq!(key.len(), 32);
/// ```
pub fn try_generate_key() -> Result<Key> {
    let mut key: Key = [0; PRIVATE_KEY_BYTES];
    OsRng.try_fill_bytes(&mut key).map_err(Error::GenerateKey)?;
    Ok(key)
}

pub fn chacha_encrypt(
    buf: &mut [u8],
    associated_data: Option<&[u8]>,
    nonce: u64,
    key: &Key,
) -> Result<()> {
    let size = buf.len();
    if size < MAC_BYTES {
        // Should have 16 bytes of extra space for the MAC
        return Err(Error::BufferSizeMismatch);
    }
    let mut final_nonce = [0; 12];
    io::Cursor::new(&mut final_nonce[4..]).write_u64::<LittleEndian>(nonce)?;
    let mac = ChaCha20Poly1305::new(key.into()).encrypt_in_place_detached(
        &final_nonce.into(),
        associated_data.unwrap_or_default(),
        &mut buf[..size - MAC_BYTES],
    )?;
    buf[size - MAC_BYTES..].copy_from_slice(mac.as_ref());
    Ok(())
}

pub fn chacha_decrypt(
    buf: &mut [u8],
    associated_data: Option<&[u8]>,
    nonce: u64,
    key: &Key,
) -> Result<()> {
    if buf.len() < MAC_BYTES {
        // Should already include the MAC
        return Err(Error::BufferSizeMismatch);
    }
    let mut final_nonce = [0; 12];
    io::Cursor::new(&mut final_nonce[4..]).write_u64::<LittleEndian>(nonce)?;
    let (buf, mac) = buf.split_at_mut(buf.len() - MAC_BYTES);
    ChaCha20Poly1305::new(key.into()).decrypt_in_place_detached(
        &final_nonce.into(),
        associated_data.unwrap_or_default(),
        buf,
        Tag::from_slice(mac),
    )?;
    Ok(())
}

pub fn xchacha_encrypt(
    buf: &mut [u8],
    associated_data: Option<&[u8]>,
    nonce: XNonce,
    key: &Key,
) -> Result<()> {
    let size = buf.len();
    if size < MAC_BYTES {
        // Should have 16 bytes of extra space for the MAC
        return Err(Error::BufferSizeMismatch);
    }
    let mac = XChaCha20Poly1305::new(key.into()).encrypt_in_place_detached(
        &nonce,
        associated_data.unwrap_or_default(),
        &mut buf[..size - MAC_BYTES],
    )?;
    buf[size - MAC_BYTES..].copy_from_slice(mac.as_ref());
    Ok(())
}

pub fn xchacha_decrypt(
    buf: &mut [u8],
    associated_data: Option<&[u8]>,
    nonce: XNonce,
    key: &Key,
) -> Result<()> {
    if buf.len() < MAC_BYTES {
        // Should already include the MAC
        return Err(Error::BufferSizeMismatch);
    }
    let (buf, mac) = buf.split_at_mut(buf.len() - MAC_BYTES);
    XChaCha20Poly1305::new(key.into()).decrypt_in_place_detached(
        &nonce,
        associated_data.unwrap_or_default(),
        buf,
        Tag::from_slice(mac),
    )?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn buf_too_small() {
        let mut buf = [0; 0];
        let nonce = 0;
        let key = generate_key();
        let result = chacha_encrypt(&mut buf, None, nonce, &key);
        assert!(result.is_err());
    }

    #[test]
    fn encrypt_decrypt_zero_sized_buf() {
        let mut buf = [0u8; MAC_BYTES]; // 16 bytes is the minimum size, which our actual buf is empty
        let nonce = 0;
        let key = generate_key();
        chacha_encrypt(&mut buf, None, nonce, &key).unwrap();

        // The buf should have been modified
        assert_ne!(buf, [0u8; MAC_BYTES]);

        chacha_decrypt(&mut buf, None, nonce, &key).unwrap();
    }
}