Skip to main content

moq_secure/
crypto.rs

1use std::convert::TryFrom;
2
3use aes_gcm::{
4    aead::{Aead, KeyInit, Payload},
5    Aes256Gcm,
6};
7use chacha20poly1305::{
8    aead::Payload as ChaChaPayload,
9    ChaCha20Poly1305,
10};
11use sha2::{Digest, Sha256};
12
13use crate::nonce::derive_nonce12;
14use crate::MoqSecureError;
15
16pub const AEAD_TAG_LEN: usize = 16;
17
18#[allow(dead_code)]
19pub(crate) const ENCRYPTION_UNENCRYPTED: u8 = 0;
20pub(crate) const ENCRYPTION_CHACHA20_POLY1305: u8 = 1;
21pub(crate) const ENCRYPTION_AES_256_GCM: u8 = 2;
22
23pub(crate) fn sha256_digest(data: &[u8]) -> [u8; 32] {
24    let mut hasher = Sha256::new();
25    hasher.update(data);
26
27    hasher.finalize().into()
28}
29
30fn split_ciphertext_and_tag(
31    combined: &[u8],
32) -> (Vec<u8>, [u8; AEAD_TAG_LEN]) {
33    debug_assert!(combined.len() >= AEAD_TAG_LEN);
34
35    let ciphertext_len = combined.len() - AEAD_TAG_LEN;
36    let ciphertext = combined[..ciphertext_len].to_vec();
37
38    let mut tag = [0u8; AEAD_TAG_LEN];
39    tag.copy_from_slice(&combined[ciphertext_len..]);
40
41    (ciphertext, tag)
42}
43
44fn combine_ciphertext_and_tag(
45    ciphertext: &[u8],
46    tag: &[u8; AEAD_TAG_LEN],
47) -> Vec<u8> {
48    let mut combined =
49        Vec::with_capacity(ciphertext.len() + AEAD_TAG_LEN);
50
51    combined.extend_from_slice(ciphertext);
52    combined.extend_from_slice(tag);
53
54    combined
55}
56
57fn encrypt_chacha20_poly1305(
58    key: &[u8; 32],
59    key_id: u8,
60    ctr: u64,
61    aad: &[u8],
62    plaintext: &[u8],
63) -> (Vec<u8>, [u8; AEAD_TAG_LEN]) {
64    let cipher = ChaCha20Poly1305::new_from_slice(key)
65        .expect("ChaCha20-Poly1305 key must be 32 bytes");
66
67    let nonce_bytes = derive_nonce12(key_id, ctr);
68    let nonce = chacha20poly1305::Nonce::try_from(nonce_bytes.as_slice())
69        .expect("ChaCha20-Poly1305 nonce must be 12 bytes");
70
71    let combined = cipher
72        .encrypt(
73            &nonce,
74            ChaChaPayload {
75                msg: plaintext,
76                aad,
77            },
78        )
79        .expect("ChaCha20-Poly1305 encryption failure should be impossible");
80
81    split_ciphertext_and_tag(&combined)
82}
83
84fn encrypt_aes256_gcm(
85    key: &[u8; 32],
86    key_id: u8,
87    ctr: u64,
88    aad: &[u8],
89    plaintext: &[u8],
90) -> (Vec<u8>, [u8; AEAD_TAG_LEN]) {
91    let cipher = Aes256Gcm::new_from_slice(key)
92        .expect("AES-256-GCM key must be 32 bytes");
93
94    let nonce_bytes = derive_nonce12(key_id, ctr);
95    let nonce = aes_gcm::Nonce::try_from(nonce_bytes.as_slice())
96        .expect("AES-256-GCM nonce must be 12 bytes");
97
98    let combined = cipher
99        .encrypt(
100            &nonce,
101            Payload {
102                msg: plaintext,
103                aad,
104            },
105        )
106        .expect("AES-256-GCM encryption failure should be impossible");
107
108    split_ciphertext_and_tag(&combined)
109}
110
111/// Encrypt according to the wire-level encryption type:
112///
113/// 1 = ChaCha20-Poly1305
114/// 2 = AES-256-GCM
115pub(crate) fn aead_encrypt(
116    encryption_type: u8,
117    key: &[u8; 32],
118    key_id: u8,
119    ctr: u64,
120    aad: &[u8],
121    plaintext: &[u8],
122) -> Result<(Vec<u8>, [u8; AEAD_TAG_LEN]), MoqSecureError> {
123    match encryption_type {
124        ENCRYPTION_CHACHA20_POLY1305 => Ok(encrypt_chacha20_poly1305(
125            key,
126            key_id,
127            ctr,
128            aad,
129            plaintext,
130        )),
131
132        ENCRYPTION_AES_256_GCM => Ok(encrypt_aes256_gcm(
133            key,
134            key_id,
135            ctr,
136            aad,
137            plaintext,
138        )),
139
140        other => Err(MoqSecureError::UnsupportedAlgorithm(other)),
141    }
142}
143
144fn decrypt_chacha20_poly1305(
145    key: &[u8; 32],
146    key_id: u8,
147    ctr: u64,
148    aad: &[u8],
149    ciphertext: &[u8],
150    tag: &[u8; AEAD_TAG_LEN],
151) -> Result<Vec<u8>, MoqSecureError> {
152    let cipher = ChaCha20Poly1305::new_from_slice(key)
153        .expect("ChaCha20-Poly1305 key must be 32 bytes");
154
155    let nonce_bytes = derive_nonce12(key_id, ctr);
156    let nonce = chacha20poly1305::Nonce::try_from(nonce_bytes.as_slice())
157        .expect("ChaCha20-Poly1305 nonce must be 12 bytes");
158
159    let combined = combine_ciphertext_and_tag(ciphertext, tag);
160
161    cipher
162        .decrypt(
163            &nonce,
164            ChaChaPayload {
165                msg: &combined,
166                aad,
167            },
168        )
169        .map_err(|_| MoqSecureError::AeadAuthFailed)
170}
171
172fn decrypt_aes256_gcm(
173    key: &[u8; 32],
174    key_id: u8,
175    ctr: u64,
176    aad: &[u8],
177    ciphertext: &[u8],
178    tag: &[u8; AEAD_TAG_LEN],
179) -> Result<Vec<u8>, MoqSecureError> {
180    let cipher = Aes256Gcm::new_from_slice(key)
181        .expect("AES-256-GCM key must be 32 bytes");
182
183    let nonce_bytes = derive_nonce12(key_id, ctr);
184    let nonce = aes_gcm::Nonce::try_from(nonce_bytes.as_slice())
185        .expect("AES-256-GCM nonce must be 12 bytes");
186
187    let combined = combine_ciphertext_and_tag(ciphertext, tag);
188
189    cipher
190        .decrypt(
191            &nonce,
192            Payload {
193                msg: &combined,
194                aad,
195            },
196        )
197        .map_err(|_| MoqSecureError::AeadAuthFailed)
198}
199
200/// Decrypt according to the wire-level encryption type:
201///
202/// 1 = ChaCha20-Poly1305
203/// 2 = AES-256-GCM
204pub(crate) fn aead_decrypt(
205    encryption_type: u8,
206    key: &[u8; 32],
207    key_id: u8,
208    ctr: u64,
209    aad: &[u8],
210    ciphertext: &[u8],
211    tag: &[u8; AEAD_TAG_LEN],
212) -> Result<Vec<u8>, MoqSecureError> {
213    match encryption_type {
214        ENCRYPTION_CHACHA20_POLY1305 => decrypt_chacha20_poly1305(
215            key,
216            key_id,
217            ctr,
218            aad,
219            ciphertext,
220            tag,
221        ),
222
223        ENCRYPTION_AES_256_GCM => decrypt_aes256_gcm(
224            key,
225            key_id,
226            ctr,
227            aad,
228            ciphertext,
229            tag,
230        ),
231
232        other => Err(MoqSecureError::UnsupportedAlgorithm(other)),
233    }
234}