trust_dns_proto/rr/dnssec/
tsig.rs1use std::ops::Range;
17use std::sync::Arc;
18
19use tracing::debug;
20
21use crate::error::ProtoErrorKind;
22use crate::error::{ProtoError, ProtoResult};
23use crate::op::{Message, MessageFinalizer, MessageVerifier};
24use crate::rr::dnssec::rdata::tsig::{
25 make_tsig_record, message_tbs, signed_bitmessage_to_buf, TsigAlgorithm, TSIG,
26};
27use crate::rr::dnssec::rdata::DNSSECRData;
28use crate::rr::{Name, RData, Record};
29use crate::xfer::DnsResponse;
30
31#[derive(Clone)]
33pub struct TSigner(Arc<TSignerInner>);
34
35struct TSignerInner {
36 key: Vec<u8>, algorithm: TsigAlgorithm,
38 signer_name: Name,
39 fudge: u16,
40}
41
42impl TSigner {
43 pub fn new(
52 key: Vec<u8>,
53 algorithm: TsigAlgorithm,
54 signer_name: Name,
55 fudge: u16,
56 ) -> ProtoResult<Self> {
57 if algorithm.supported() {
58 Ok(Self(Arc::new(TSignerInner {
59 key,
60 algorithm,
61 signer_name,
62 fudge,
63 })))
64 } else {
65 Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(algorithm).into())
66 }
67 }
68
69 pub fn key(&self) -> &[u8] {
71 &self.0.key
72 }
73
74 pub fn algorithm(&self) -> &TsigAlgorithm {
76 &self.0.algorithm
77 }
78
79 pub fn signer_name(&self) -> &Name {
81 &self.0.signer_name
82 }
83
84 pub fn fudge(&self) -> u16 {
89 self.0.fudge
90 }
91
92 pub fn sign(&self, tbs: &[u8]) -> ProtoResult<Vec<u8>> {
94 self.0.algorithm.mac_data(&self.0.key, tbs)
95 }
96
97 pub fn sign_message(&self, message: &Message, pre_tsig: &TSIG) -> ProtoResult<Vec<u8>> {
99 message_tbs(None, message, pre_tsig, &self.0.signer_name).and_then(|tbs| self.sign(&tbs))
100 }
101
102 pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> ProtoResult<()> {
104 self.0.algorithm.verify_mac(&self.0.key, tbv, tag)
105 }
106
107 pub fn verify_message_byte(
125 &self,
126 previous_hash: Option<&[u8]>,
127 message: &[u8],
128 first_message: bool,
129 ) -> ProtoResult<(Vec<u8>, Range<u64>, u64)> {
130 let (tbv, record) = signed_bitmessage_to_buf(previous_hash, message, first_message)?;
131 let tsig = if let Some(RData::DNSSEC(DNSSECRData::TSIG(tsig))) = record.data() {
132 tsig
133 } else {
134 unreachable!("tsig::signed_message_to_buff always returns a TSIG record")
135 };
136
137 if record.name() != &self.0.signer_name || tsig.algorithm() != &self.0.algorithm {
140 return Err(ProtoErrorKind::TsigWrongKey.into());
141 }
142
143 if tsig.mac().len() < tsig.algorithm().output_len()? {
148 return Err(ProtoError::from("Please file an issue with https://github.com/bluejekyll/trust-dns to support truncated HMACs with TSIG"));
149 }
150
151 let mac = tsig.mac();
153 self.verify(&tbv, mac)
154 .map_err(|_e| ProtoError::from("tsig validation error: invalid signature"))?;
155
156 Ok((
171 tsig.mac().to_vec(),
172 Range {
173 start: tsig.time() - tsig.fudge() as u64,
174 end: tsig.time() + tsig.fudge() as u64,
175 },
176 tsig.time(),
177 ))
178 }
179}
180
181impl MessageFinalizer for TSigner {
182 fn finalize_message(
183 &self,
184 message: &Message,
185 current_time: u32,
186 ) -> ProtoResult<(Vec<Record>, Option<MessageVerifier>)> {
187 debug!("signing message: {:?}", message);
188 let current_time = current_time as u64;
189
190 let pre_tsig = TSIG::new(
191 self.0.algorithm.clone(),
192 current_time,
193 self.0.fudge,
194 Vec::new(),
195 message.id(),
196 0,
197 Vec::new(),
198 );
199 let mut signature: Vec<u8> = self.sign_message(message, &pre_tsig)?;
200 let tsig = make_tsig_record(
201 self.0.signer_name.clone(),
202 pre_tsig.set_mac(signature.clone()),
203 );
204 let self2 = self.clone();
205 let mut remote_time = 0;
206 let verifier = move |dns_response: &[u8]| {
207 let (last_sig, range, rt) = self2.verify_message_byte(
208 Some(signature.as_ref()),
209 dns_response,
210 remote_time == 0,
211 )?;
212 if rt >= remote_time && range.contains(¤t_time)
213 {
215 signature = last_sig;
216 remote_time = rt;
217 Ok(DnsResponse::new(
218 Message::from_vec(dns_response)?,
219 dns_response.to_vec(),
220 ))
221 } else {
222 Err(ProtoError::from("tsig validation error: outdated response"))
223 }
224 };
225 Ok((vec![tsig], Some(Box::new(verifier))))
226 }
227}
228
229#[cfg(test)]
230#[cfg(any(feature = "dnssec-ring", feature = "dnssec-openssl"))]
231
232mod tests {
233 #![allow(clippy::dbg_macro, clippy::print_stdout)]
234
235 use crate::op::{Message, Query};
236 use crate::rr::Name;
237 use crate::serialize::binary::BinEncodable;
238
239 use super::*;
240 fn assert_send_and_sync<T: Send + Sync>() {}
241
242 #[test]
243 fn test_send_and_sync() {
244 assert_send_and_sync::<TSigner>();
245 }
246
247 #[test]
248 fn test_sign_and_verify_message_tsig() {
249 let time_begin = 1609459200u64;
250 let fudge = 300u64;
251 let origin: Name = Name::parse("example.com.", None).unwrap();
252 let key_name: Name = Name::from_ascii("key_name").unwrap();
253 let mut question: Message = Message::new();
254 let mut query: Query = Query::new();
255 query.set_name(origin);
256 question.add_query(query);
257
258 let sig_key = b"some_key".to_vec();
259 let signer =
260 TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap();
261
262 assert!(question.signature().is_empty());
263 question
264 .finalize(&signer, time_begin as u32)
265 .expect("should have signed");
266 assert!(!question.signature().is_empty());
267
268 let (_, validity_range, _) = signer
269 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
270 .unwrap();
271 assert!(validity_range.contains(&(time_begin + fudge / 2))); assert!(validity_range.contains(&(time_begin - fudge / 2))); assert!(!validity_range.contains(&(time_begin + fudge * 2))); assert!(!validity_range.contains(&(time_begin - fudge * 2))); }
276
277 fn get_message_and_signer() -> (Message, TSigner) {
279 let time_begin = 1609459200u64;
280 let fudge = 300u64;
281 let origin: Name = Name::parse("example.com.", None).unwrap();
282 let key_name: Name = Name::from_ascii("key_name").unwrap();
283 let mut question: Message = Message::new();
284 let mut query: Query = Query::new();
285 query.set_name(origin);
286 question.add_query(query);
287
288 let sig_key = b"some_key".to_vec();
289 let signer =
290 TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap();
291
292 assert!(question.signature().is_empty());
293 question
294 .finalize(&signer, time_begin as u32)
295 .expect("should have signed");
296 assert!(!question.signature().is_empty());
297
298 assert!(signer
300 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
301 .is_ok());
302
303 (question, signer)
304 }
305
306 #[test]
307 fn test_sign_and_verify_message_tsig_reject_keyname() {
308 let (mut question, signer) = get_message_and_signer();
309
310 let other_name: Name = Name::from_ascii("other_name").unwrap();
311 let mut signature = question.take_signature().remove(0);
312 signature.set_name(other_name);
313 question.add_tsig(signature);
314
315 assert!(signer
316 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
317 .is_err());
318 }
319
320 #[test]
321 fn test_sign_and_verify_message_tsig_reject_invalid_mac() {
322 let (mut question, signer) = get_message_and_signer();
323
324 let mut query: Query = Query::new();
325 let origin: Name = Name::parse("example.net.", None).unwrap();
326 query.set_name(origin);
327 question.add_query(query);
328
329 assert!(signer
330 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
331 .is_err());
332 }
333
334 #[test]
335 #[cfg(feature = "hmac_truncation")] fn test_sign_and_verify_message_tsig_truncation() {
337 let (mut question, signer) = get_message_and_signer();
338
339 {
340 let mut signature = question.take_signature().remove(0);
341 if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
342 let mut mac = tsig.mac().to_vec();
343 mac.push(0); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
345 } else {
346 panic!("should have been a TSIG");
347 }
348 question.add_tsig(signature);
349 }
350
351 assert!(signer
353 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
354 .is_err());
355 {
356 let mut signature = question.take_signature().remove(0);
357 if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
358 let mac = tsig.mac()[..256 / 8].to_vec();
360 std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
361 } else {
362 panic!("should have been a TSIG");
363 }
364 question.add_tsig(signature);
365 }
366
367 assert!(signer
369 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
370 .is_ok());
371
372 {
373 let mut signature = question.take_signature().remove(0);
374 if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() {
375 let mac = tsig.mac()[..240 / 8].to_vec();
377 std::mem::swap(tsig, &mut tsig.clone().set_mac(mac));
378 } else {
379 panic!("should have been a TSIG");
380 }
381 question.add_tsig(signature);
382 }
383
384 assert!(signer
385 .verify_message_byte(None, &question.to_bytes().unwrap(), true)
386 .is_err());
387 }
388}