Decrypt a PQ query that carries an X-Wing ciphertext, and prepare a freshly issued resumption ticket for the response.
(
wrapped_packet: &[u8],
params: &Arc<DNSCryptEncryptionParams>,
pq_ticket_key: &pq::TicketKey,
)
| 139 | /// Decrypt a PQ query that carries an X-Wing ciphertext, and prepare a |
| 140 | /// freshly issued resumption ticket for the response. |
| 141 | fn decrypt_pq_ciphertext( |
| 142 | wrapped_packet: &[u8], |
| 143 | params: &Arc<DNSCryptEncryptionParams>, |
| 144 | pq_ticket_key: &pq::TicketKey, |
| 145 | ) -> Result<(EncryptionParams, Vec<u8>), Error> { |
| 146 | let pq_params = params.pq().ok_or_else(|| anyhow!("No PQ params"))?; |
| 147 | let cm = DNSCRYPT_QUERY_MAGIC_SIZE; |
| 148 | let ct_len = pq::XWING_CT_SIZE; |
| 149 | let nonce_size = DNSCRYPT_QUERY_NONCE_SIZE; |
| 150 | ensure!( |
| 151 | wrapped_packet.len() >= cm + ct_len + nonce_size + DNSCRYPT_MAC_SIZE + DNS_HEADER_SIZE, |
| 152 | "Short PQ query" |
| 153 | ); |
| 154 | let mut client_magic = [0u8; 8]; |
| 155 | client_magic.copy_from_slice(&wrapped_packet[..cm]); |
| 156 | let ct = &wrapped_packet[cm..cm + ct_len]; |
| 157 | let client_nonce = &wrapped_packet[cm + ct_len..cm + ct_len + nonce_size]; |
| 158 | let encrypted_packet = &wrapped_packet[cm + ct_len + nonce_size..]; |
| 159 | |
| 160 | let kem_ss = pq_params.keypair().decapsulate(ct)?; |
| 161 | let cert_ctx = pq_params.cert_context(); |
| 162 | let es_version = pq_params.es_version(); |
| 163 | let shared_key = pq::derive_shared_key(&kem_ss, &es_version, &client_magic, cert_ctx, ct); |
| 164 | |
| 165 | let mut nonce = [0u8; DNSCRYPT_FULL_NONCE_SIZE]; |
| 166 | nonce[..nonce_size].copy_from_slice(client_nonce); |
| 167 | let packet = shared_key.decrypt(&nonce, encrypted_packet)?; |
| 168 | |
| 169 | let sk_bytes = *shared_key.as_raw_bytes(); |
| 170 | let rs = pq::resume_secret(&sk_bytes, &client_magic, client_nonce); |
| 171 | let mut ticket_nonce = [0u8; pq::TICKET_NONCE_SIZE]; |
| 172 | rand::rng().fill_bytes(&mut ticket_nonce); |
| 173 | let ticket_expiry = now().saturating_add(pq::PQ_TICKET_LIFETIME); |
| 174 | let peh = pq_params.profile_extension_hash(); |
| 175 | let serial = pq_params.serial(); |
| 176 | let ts_end = pq_params.ts_end(); |
| 177 | let tp = pq::ticket_plain(&rs, &es_version, &client_magic, &serial, &ts_end, ticket_expiry, &peh); |
| 178 | let ticket = pq::seal_ticket(pq_ticket_key, &ticket_nonce, &tp); |
| 179 | let control = pq::control_block(pq::PQ_TICKET_LIFETIME, &ticket); |
| 180 | debug!("PQ X-Wing query decapsulated; resumption ticket issued"); |
| 181 | |
| 182 | rand::rng().fill_bytes(&mut nonce[nonce_size..]); |
| 183 | Ok(( |
| 184 | EncryptionParams::Pq { |
| 185 | shared_key, |
| 186 | nonce, |
| 187 | control, |
| 188 | }, |
| 189 | packet, |
| 190 | )) |
| 191 | } |
| 192 | |
| 193 | /// Decrypt a resumed PQ query, validating its ticket and deriving the |
| 194 | /// per-query key without a KEM decapsulation. |
no test coverage detected