contact_tracing/
dtkey.rs

1use std::fmt;
2
3use bytes::{BufMut, BytesMut};
4use derive_more::{Display, Error};
5use hkdf::Hkdf;
6use hmac::{Hmac, Mac};
7use sha2::Sha256;
8
9#[cfg(feature = "chrono")]
10use chrono::{DateTime, Utc};
11
12use crate::rpi::Rpi;
13use crate::tkey::TracingKey;
14use crate::utils::Base64DebugFmtHelper;
15
16#[cfg(feature = "chrono")]
17use crate::utils::day_number_for_timestamp;
18
19/// A compact representation of contact numbers.
20#[derive(Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
21pub struct DailyTracingKey {
22    bytes: [u8; 16],
23}
24
25impl fmt::Debug for DailyTracingKey {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        f.debug_tuple("DailyTracingKey")
28            .field(&Base64DebugFmtHelper(self))
29            .finish()
30    }
31}
32
33impl DailyTracingKey {
34    /// Returns the daily tracing key for a day.
35    pub fn for_day(tk: &TracingKey, day: u32) -> DailyTracingKey {
36        let h = Hkdf::<Sha256>::new(None, tk.as_bytes());
37        let mut info = BytesMut::from(&"CT-DTK"[..]);
38        info.put_u32_le(day);
39        let mut out = [0u8; 16];
40        h.expand(&info, &mut out).unwrap();
41        DailyTracingKey::from_bytes(&out[..]).unwrap()
42    }
43
44    /// Returns the daily tracing key for today.
45    #[cfg(feature = "chrono")]
46    pub fn for_today(tk: &TracingKey) -> DailyTracingKey {
47        DailyTracingKey::for_timestamp(tk, &Utc::now())
48    }
49
50    /// Returns the daily tracing key for a timestamp.
51    #[cfg(feature = "chrono")]
52    pub fn for_timestamp(tk: &TracingKey, timestamp: &DateTime<Utc>) -> DailyTracingKey {
53        DailyTracingKey::for_day(tk, day_number_for_timestamp(timestamp))
54    }
55
56    /// Creates a daily tracing key from raw bytes.
57    pub fn from_bytes(b: &[u8]) -> Result<DailyTracingKey, InvalidDailyTracingKey> {
58        if b.len() != 16 {
59            return Err(InvalidDailyTracingKey);
60        }
61        let mut bytes = [0u8; 16];
62        bytes.copy_from_slice(b);
63        Ok(DailyTracingKey { bytes })
64    }
65
66    /// Returns the bytes behind the daily tracing key
67    pub fn as_bytes(&self) -> &[u8] {
68        &self.bytes
69    }
70
71    /// Generates all RPIs for a day.
72    ///
73    /// If you need the TINs too just use `.enumerate()`.
74    pub fn iter_rpis(&self) -> impl Iterator<Item = Rpi> {
75        let clone = *self;
76        let mut tin = 0;
77        std::iter::from_fn(move || {
78            clone.get_rpi_for_tin(tin).map(|rv| {
79                tin += 1;
80                rv
81            })
82        })
83    }
84
85    /// Returns the RPI for a time interval number.
86    ///
87    /// If the time interval is out of range this returns `None`
88    pub fn get_rpi_for_tin(&self, tin: u8) -> Option<Rpi> {
89        if tin > 143 {
90            return None;
91        }
92
93        let mut hmac = Hmac::<Sha256>::new_varkey(&self.as_bytes()).unwrap();
94        let mut info = BytesMut::from(&"CT-RPI"[..]);
95        info.put_u8(tin);
96        hmac.input(&info[..]);
97        let result = hmac.result();
98        let bytes = &result.code()[..];
99        Some(Rpi::from_bytes(&bytes[..16]).unwrap())
100    }
101}
102
103/// Returned if a daily tracing key is invalid.
104#[derive(Error, Display, Debug)]
105#[display(fmt = "invalid daily tracing key")]
106pub struct InvalidDailyTracingKey;
107
108#[cfg(feature = "base64")]
109mod base64_impl {
110    use super::*;
111    use std::{fmt, str};
112
113    impl str::FromStr for DailyTracingKey {
114        type Err = InvalidDailyTracingKey;
115
116        fn from_str(value: &str) -> Result<DailyTracingKey, InvalidDailyTracingKey> {
117            let mut bytes = [0u8; 16];
118            if value.len() != 22 {
119                return Err(InvalidDailyTracingKey);
120            }
121            base64_::decode_config_slice(value, base64_::URL_SAFE_NO_PAD, &mut bytes[..])
122                .map_err(|_| InvalidDailyTracingKey)?;
123            Ok(DailyTracingKey { bytes })
124        }
125    }
126
127    #[cfg(feature = "base64")]
128    impl fmt::Display for DailyTracingKey {
129        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130            let mut buf = [0u8; 50];
131            let len = base64_::encode_config_slice(self.bytes, base64_::URL_SAFE_NO_PAD, &mut buf);
132            f.write_str(unsafe { std::str::from_utf8_unchecked(&buf[..len]) })
133        }
134    }
135}
136
137#[cfg(feature = "serde")]
138mod serde_impl {
139    use super::*;
140
141    use serde_::de::Deserializer;
142    use serde_::ser::Serializer;
143    use serde_::{Deserialize, Serialize};
144
145    impl Serialize for DailyTracingKey {
146        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147        where
148            S: Serializer,
149        {
150            if serializer.is_human_readable() {
151                serializer.serialize_str(&self.to_string())
152            } else {
153                serializer.serialize_bytes(self.as_bytes())
154            }
155        }
156    }
157
158    impl<'de> Deserialize<'de> for DailyTracingKey {
159        fn deserialize<D>(deserializer: D) -> Result<DailyTracingKey, D::Error>
160        where
161            D: Deserializer<'de>,
162        {
163            use serde_::de::Error;
164            if deserializer.is_human_readable() {
165                let s = String::deserialize(deserializer).map_err(D::Error::custom)?;
166                s.parse().map_err(D::Error::custom)
167            } else {
168                let buf = Vec::<u8>::deserialize(deserializer).map_err(D::Error::custom)?;
169                DailyTracingKey::from_bytes(&buf).map_err(D::Error::custom)
170            }
171        }
172    }
173}