Decrypt a resumed PQ query, validating its ticket and deriving the per-query key without a KEM decapsulation.
(
wrapped_packet: &[u8],
dnscrypt_encryption_params_set: &[Arc<DNSCryptEncryptionParams>],
pq_ticket_key: &pq::TicketKey,
)
| 193 | /// Decrypt a resumed PQ query, validating its ticket and deriving the |
| 194 | /// per-query key without a KEM decapsulation. |
| 195 | fn decrypt_pq_resumed( |
| 196 | wrapped_packet: &[u8], |
| 197 | dnscrypt_encryption_params_set: &[Arc<DNSCryptEncryptionParams>], |
| 198 | pq_ticket_key: &pq::TicketKey, |
| 199 | ) -> Result<(EncryptionParams, Vec<u8>), Error> { |
| 200 | let magic = pq::PQ_RESUME_MAGIC.len(); |
| 201 | let nonce_size = DNSCRYPT_QUERY_NONCE_SIZE; |
| 202 | ensure!(wrapped_packet.len() >= magic + 2, "Short resumed query"); |
| 203 | let ticket_len = |
| 204 | u16::from_be_bytes([wrapped_packet[magic], wrapped_packet[magic + 1]]) as usize; |
| 205 | let ticket_off = magic + 2; |
| 206 | ensure!( |
| 207 | wrapped_packet.len() >= ticket_off + ticket_len + nonce_size + DNSCRYPT_MAC_SIZE + DNS_HEADER_SIZE, |
| 208 | "Short resumed query" |
| 209 | ); |
| 210 | let ticket = &wrapped_packet[ticket_off..ticket_off + ticket_len]; |
| 211 | let client_nonce = &wrapped_packet[ticket_off + ticket_len..ticket_off + ticket_len + nonce_size]; |
| 212 | let encrypted_packet = &wrapped_packet[ticket_off + ticket_len + nonce_size..]; |
| 213 | |
| 214 | let opened = pq::open_ticket(pq_ticket_key, ticket)?; |
| 215 | let fields = pq::parse_ticket_plain(&opened)?; |
| 216 | ensure!(fields.ticket_expiry > now(), "Expired ticket"); |
| 217 | ensure!( |
| 218 | dnscrypt_encryption_params_set.iter().any(|p| p.pq().is_some_and(|pq| { |
| 219 | pq.client_magic() == fields.client_magic && pq.es_version() == fields.es_version |
| 220 | })), |
| 221 | "Ticket does not match a current certificate" |
| 222 | ); |
| 223 | |
| 224 | let resumed_key = |
| 225 | pq::resumed_shared_key(&fields.resume_secret, &fields.client_magic, client_nonce, ticket); |
| 226 | let mut nonce = [0u8; DNSCRYPT_FULL_NONCE_SIZE]; |
| 227 | nonce[..nonce_size].copy_from_slice(client_nonce); |
| 228 | let packet = resumed_key.decrypt(&nonce, encrypted_packet)?; |
| 229 | debug!("PQ resumed query accepted (no decapsulation)"); |
| 230 | rand::rng().fill_bytes(&mut nonce[nonce_size..]); |
| 231 | Ok(( |
| 232 | EncryptionParams::Pq { |
| 233 | shared_key: resumed_key, |
| 234 | nonce, |
| 235 | control: Vec::new(), |
| 236 | }, |
| 237 | packet, |
| 238 | )) |
| 239 | } |
| 240 | |
| 241 | pub fn encrypt( |
| 242 | packet: Vec<u8>, |
no test coverage detected