1use base64urlsafedata::Base64UrlSafeData;
2use openssl::error::ErrorStack as OpenSSLErrorStack;
3use openssl::{hash, x509};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct DeviceDescription {
11 pub(crate) en: String,
12 pub(crate) localised: BTreeMap<String, String>,
13}
14
15impl DeviceDescription {
16 pub fn description_en(&self) -> &str {
18 self.en.as_str()
19 }
20
21 pub fn description_localised(&self) -> &BTreeMap<String, String> {
25 &self.localised
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct SerialisableAttestationCa {
32 pub(crate) ca: Base64UrlSafeData,
33 pub(crate) aaguids: BTreeMap<Uuid, DeviceDescription>,
34 pub(crate) blanket_allow: bool,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42#[serde(
43 try_from = "SerialisableAttestationCa",
44 into = "SerialisableAttestationCa"
45)]
46pub struct AttestationCa {
47 ca: x509::X509,
49 aaguids: BTreeMap<Uuid, DeviceDescription>,
53 blanket_allow: bool,
54}
55
56#[allow(clippy::from_over_into)]
57impl Into<SerialisableAttestationCa> for AttestationCa {
58 fn into(self) -> SerialisableAttestationCa {
59 SerialisableAttestationCa {
60 ca: Base64UrlSafeData::from(self.ca.to_der().expect("Invalid DER")),
61 aaguids: self.aaguids,
62 blanket_allow: self.blanket_allow,
63 }
64 }
65}
66
67impl TryFrom<SerialisableAttestationCa> for AttestationCa {
68 type Error = OpenSSLErrorStack;
69
70 fn try_from(data: SerialisableAttestationCa) -> Result<Self, Self::Error> {
71 Ok(AttestationCa {
72 ca: x509::X509::from_der(data.ca.as_slice())?,
73 aaguids: data.aaguids,
74 blanket_allow: data.blanket_allow,
75 })
76 }
77}
78
79impl AttestationCa {
80 pub fn ca(&self) -> &x509::X509 {
81 &self.ca
82 }
83
84 pub fn aaguids(&self) -> &BTreeMap<Uuid, DeviceDescription> {
85 &self.aaguids
86 }
87
88 pub fn blanket_allow(&self) -> bool {
89 self.blanket_allow
90 }
91
92 pub fn get_kid(&self) -> Result<Vec<u8>, OpenSSLErrorStack> {
94 self.ca
95 .digest(hash::MessageDigest::sha256())
96 .map(|bytes| bytes.to_vec())
97 }
98
99 fn insert_device(
100 &mut self,
101 aaguid: Uuid,
102 desc_english: String,
103 desc_localised: BTreeMap<String, String>,
104 ) {
105 self.blanket_allow = false;
106 self.aaguids.insert(
107 aaguid,
108 DeviceDescription {
109 en: desc_english,
110 localised: desc_localised,
111 },
112 );
113 }
114
115 fn new_from_pem(data: &[u8]) -> Result<Self, OpenSSLErrorStack> {
116 Ok(AttestationCa {
117 ca: x509::X509::from_pem(data)?,
118 aaguids: BTreeMap::default(),
119 blanket_allow: true,
120 })
121 }
122
123 fn union(&mut self, other: &Self) {
124 if self.blanket_allow || other.blanket_allow {
126 self.blanket_allow = true;
127 self.aaguids.clear();
128 } else {
129 self.blanket_allow = false;
130 for (o_aaguid, o_device) in other.aaguids.iter() {
131 self.aaguids
133 .entry(*o_aaguid)
134 .or_insert_with(|| o_device.clone());
135 }
136 }
137 }
138
139 fn intersection(&mut self, other: &Self) {
140 if other.blanket_allow() {
143 } else if self.blanket_allow {
145 self.blanket_allow = false;
147 self.aaguids = other.aaguids.clone();
148 } else {
149 self.aaguids
151 .retain(|s_aaguid, _| other.aaguids.contains_key(s_aaguid))
152 }
153 }
154
155 fn can_retain(&self) -> bool {
156 self.blanket_allow || !self.aaguids.is_empty()
158 }
159}
160
161#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
163pub struct AttestationCaList {
164 cas: BTreeMap<Base64UrlSafeData, AttestationCa>,
166}
167
168impl TryFrom<&[u8]> for AttestationCaList {
169 type Error = OpenSSLErrorStack;
170
171 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
172 let mut new = Self::default();
173 let att_ca = AttestationCa::new_from_pem(data)?;
174 new.insert(att_ca)?;
175 Ok(new)
176 }
177}
178
179impl AttestationCaList {
180 pub fn cas(&self) -> &BTreeMap<Base64UrlSafeData, AttestationCa> {
181 &self.cas
182 }
183
184 pub fn clear(&mut self) {
185 self.cas.clear()
186 }
187
188 pub fn len(&self) -> usize {
189 self.cas.len()
190 }
191
192 pub fn is_empty(&self) -> bool {
194 self.cas.is_empty()
195 }
196
197 pub fn insert(
199 &mut self,
200 att_ca: AttestationCa,
201 ) -> Result<Option<AttestationCa>, OpenSSLErrorStack> {
202 let att_ca_dgst = att_ca.get_kid()?;
204 Ok(self.cas.insert(att_ca_dgst.into(), att_ca))
205 }
206
207 pub fn union(&mut self, other: &Self) {
209 for (o_kid, o_att_ca) in other.cas.iter() {
210 if let Some(s_att_ca) = self.cas.get_mut(o_kid) {
211 s_att_ca.union(o_att_ca)
212 } else {
213 self.cas.insert(o_kid.clone(), o_att_ca.clone());
214 }
215 }
216 }
217
218 pub fn intersection(&mut self, other: &Self) {
220 self.cas.retain(|s_kid, s_att_ca| {
221 if let Some(o_att_ca) = other.cas.get(s_kid) {
223 s_att_ca.intersection(o_att_ca);
225 if s_att_ca.can_retain() {
226 true
228 } else {
229 false
231 }
232 } else {
233 false
235 }
236 })
237 }
238}
239
240#[derive(Default)]
241pub struct AttestationCaListBuilder {
242 cas: BTreeMap<Vec<u8>, AttestationCa>,
243}
244
245impl AttestationCaListBuilder {
246 pub fn new() -> Self {
247 Self::default()
248 }
249
250 pub fn insert_device_x509(
251 &mut self,
252 ca: x509::X509,
253 aaguid: Uuid,
254 desc_english: String,
255 desc_localised: BTreeMap<String, String>,
256 ) -> Result<(), OpenSSLErrorStack> {
257 let kid = ca
258 .digest(hash::MessageDigest::sha256())
259 .map(|bytes| bytes.to_vec())?;
260
261 let mut att_ca = if let Some(att_ca) = self.cas.remove(&kid) {
262 att_ca
263 } else {
264 AttestationCa {
265 ca,
266 aaguids: BTreeMap::default(),
267 blanket_allow: false,
268 }
269 };
270
271 att_ca.insert_device(aaguid, desc_english, desc_localised);
272
273 self.cas.insert(kid, att_ca);
274
275 Ok(())
276 }
277
278 pub fn insert_device_der(
279 &mut self,
280 ca_der: &[u8],
281 aaguid: Uuid,
282 desc_english: String,
283 desc_localised: BTreeMap<String, String>,
284 ) -> Result<(), OpenSSLErrorStack> {
285 let ca = x509::X509::from_der(ca_der)?;
286 self.insert_device_x509(ca, aaguid, desc_english, desc_localised)
287 }
288
289 pub fn insert_device_pem(
290 &mut self,
291 ca_pem: &[u8],
292 aaguid: Uuid,
293 desc_english: String,
294 desc_localised: BTreeMap<String, String>,
295 ) -> Result<(), OpenSSLErrorStack> {
296 let ca = x509::X509::from_pem(ca_pem)?;
297 self.insert_device_x509(ca, aaguid, desc_english, desc_localised)
298 }
299
300 pub fn build(self) -> AttestationCaList {
301 let cas = self
302 .cas
303 .into_iter()
304 .map(|(kid, att_ca)| (kid.into(), att_ca))
305 .collect();
306
307 AttestationCaList { cas }
308 }
309}