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
111pub(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
200pub(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}