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
#![doc = include_str!("../README.md")]
use std::ops::Sub;

use chacha20poly1305::aead::{
    generic_array::{ArrayLength, GenericArray},
    stream::{Decryptor, Encryptor, NewStream, NonceSize, StreamPrimitive},
};

use tokio::io::{AsyncRead, AsyncWrite};

mod read_half;
pub use read_half::ReadHalf;

mod write_half;
pub use write_half::WriteHalf;

pub const DEFAULT_BUFFER_SIZE: usize = 4096;
pub const DEFAULT_CHUNK_SIZE: usize = 1024;

/// Creates a pair of [ReadHalf] and [WriteHalf] with default buffer size of
/// [self::DEFAULT_BUFFER_SIZE] and chunk size of [self::DEFAULT_CHUNK_SIZE]  
///
/// ```rust
/// use chacha20poly1305::aead::stream::{DecryptorLE31, EncryptorLE31};
/// use chacha20poly1305::XChaCha20Poly1305;
///
/// use async_encrypted_stream::{ReadHalf, WriteHalf, encrypted_stream};
///
/// let key = [0u8; 32];
/// let nonce = [0u8; 20];
///
/// let (rx, tx) = tokio::io::duplex(4096);
/// let (mut reader, mut writer): (
///     ReadHalf<_, DecryptorLE31<XChaCha20Poly1305>>,
///     WriteHalf<_, EncryptorLE31<XChaCha20Poly1305>>,
/// ) = encrypted_stream(rx, tx, key.as_ref().into(), nonce.as_ref().into());
/// ````
pub fn encrypted_stream<R: AsyncRead, W: AsyncWrite, A, S>(
    read: R,
    write: W,
    key: &GenericArray<u8, A::KeySize>,
    nonce: &GenericArray<u8, NonceSize<A, S>>,
) -> (ReadHalf<R, Decryptor<A, S>>, WriteHalf<W, Encryptor<A, S>>)
where
    S: StreamPrimitive<A> + NewStream<A>,
    A: chacha20poly1305::AeadInPlace + chacha20poly1305::KeyInit,
    A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
    NonceSize<A, S>: ArrayLength<u8>,
{
    encrypted_stream_with_capacity(
        read,
        write,
        key,
        nonce,
        DEFAULT_BUFFER_SIZE,
        DEFAULT_CHUNK_SIZE,
    )
}

/// Creates a pair of [ReadHalf] and [WriteHalf] with default buffer size of
/// `buffer_size` and chunk size of `chunk_size`  
///
/// ```rust
/// use chacha20poly1305::aead::stream::{DecryptorLE31, EncryptorLE31};
/// use chacha20poly1305::XChaCha20Poly1305;
///
/// use async_encrypted_stream::{ReadHalf, WriteHalf, encrypted_stream_with_capacity};
///
/// let key = [0u8; 32];
/// let nonce = [0u8; 20];
///
/// let (rx, tx) = tokio::io::duplex(4096);
/// let (mut reader, mut writer): (
///     ReadHalf<_, DecryptorLE31<XChaCha20Poly1305>>,
///     WriteHalf<_, EncryptorLE31<XChaCha20Poly1305>>,
/// ) = encrypted_stream_with_capacity(rx, tx, key.as_ref().into(), nonce.as_ref().into(), 4096,
/// 512);
/// ````
pub fn encrypted_stream_with_capacity<R: AsyncRead, W: AsyncWrite, A, S>(
    read: R,
    write: W,
    key: &GenericArray<u8, A::KeySize>,
    nonce: &GenericArray<u8, NonceSize<A, S>>,
    buffer_size: usize,
    chunk_size: usize,
) -> (ReadHalf<R, Decryptor<A, S>>, WriteHalf<W, Encryptor<A, S>>)
where
    S: StreamPrimitive<A> + NewStream<A>,
    A: chacha20poly1305::AeadInPlace + chacha20poly1305::KeyInit,
    A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
    NonceSize<A, S>: ArrayLength<u8>,
{
    let encryptor = Encryptor::new(key, nonce);
    let decryptor = Decryptor::new(key, nonce);

    (
        ReadHalf::with_capacity(read, decryptor, buffer_size),
        WriteHalf::with_capacity(write, encryptor, buffer_size, chunk_size),
    )
}

#[cfg(test)]
fn get_key<const S: usize>(plain_key: &str, salt: &str) -> [u8; S] {
    const ITERATIONS: u32 = 4096;
    pbkdf2::pbkdf2_hmac_array::<sha2::Sha256, S>(plain_key.as_bytes(), salt.as_bytes(), ITERATIONS)
}

#[cfg(test)]
mod tests {
    use std::{assert_eq, time::Duration};

    use chacha20poly1305::{
        aead::stream::{DecryptorLE31, EncryptorLE31},
        XChaCha20Poly1305,
    };
    use tokio::io::{AsyncReadExt, AsyncWriteExt};

    use super::*;

    #[tokio::test]
    pub async fn test_big_transfer() {
        let key: [u8; 32] = get_key("key", "group");
        let nonce = [0u8; 20];

        let (rx, tx) = tokio::io::duplex(4096);
        let (mut reader, mut writer): (
            ReadHalf<_, DecryptorLE31<XChaCha20Poly1305>>,
            WriteHalf<_, EncryptorLE31<XChaCha20Poly1305>>,
        ) = super::encrypted_stream(rx, tx, key.as_ref().into(), nonce.as_ref().into());

        let size = 1024 * 4;
        tokio::spawn(async move {
            let content = vec![100u8; size];
            let _ = writer.write(&content).await;
            let _ = writer.flush().await;
        });

        tokio::time::sleep(Duration::from_millis(100)).await;

        let mut bytes_expected = size;
        let mut read_buf = vec![0u8; 1024];
        while let Ok(bytes_read) = reader.read(&mut read_buf[..]).await {
            if bytes_read == 0 {
                break;
            }

            assert!(read_buf[..bytes_read].iter().all(|b| *b == 100));
            bytes_expected -= bytes_read;
        }

        assert_eq!(0, bytes_expected);
    }
}