1// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use xso::{text::Base64, AsXml, FromXml};
8
9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
10use crate::ns;
11
12generate_elem_id!(
13 /// The name of a certificate.
14 Name,
15 "name",
16 SASL_CERT
17);
18
19/// An X.509 certificate.
20#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
21#[xml(namespace = ns::SASL_CERT, name = "x509cert")]
22pub struct Cert {
23 /// The BER X.509 data.
24 #[xml(text = Base64)]
25 pub data: Vec<u8>,
26}
27
28/// For the client to upload an X.509 certificate.
29#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
30#[xml(namespace = ns::SASL_CERT, name = "append")]
31pub struct Append {
32 /// The name of this certificate.
33 #[xml(child)]
34 pub name: Name,
35
36 /// The X.509 certificate to set.
37 #[xml(child)]
38 pub cert: Cert,
39
40 /// This client is forbidden from managing certificates.
41 #[xml(flag(name = "no-cert-management"))]
42 pub no_cert_management: bool,
43}
44
45impl IqSetPayload for Append {}
46
47/// Client requests the current list of X.509 certificates.
48#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
49#[xml(namespace = ns::SASL_CERT, name = "items")]
50pub struct ListCertsQuery;
51
52impl IqGetPayload for ListCertsQuery {}
53
54generate_elem_id!(
55 /// One resource currently using a certificate.
56 Resource,
57 "resource",
58 SASL_CERT
59);
60
61/// A list of resources currently using this certificate.
62#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
63#[xml(namespace = ns::SASL_CERT, name = "users")]
64pub struct Users {
65 /// Resources currently using this certificate.
66 #[xml(child(n = ..))]
67 pub resources: Vec<Resource>,
68}
69
70/// An X.509 certificate being set for this user.
71#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
72#[xml(namespace = ns::SASL_CERT, name = "item")]
73pub struct Item {
74 /// The name of this certificate.
75 #[xml(child)]
76 pub name: Name,
77
78 /// The X.509 certificate to set.
79 #[xml(child)]
80 pub cert: Cert,
81
82 /// This client is forbidden from managing certificates.
83 #[xml(flag(name = "no-cert-management"))]
84 pub no_cert_management: bool,
85
86 /// List of resources currently using this certificate.
87 #[xml(child(default))]
88 pub users: Option<Users>,
89}
90
91/// Server answers with the current list of X.509 certificates.
92#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
93#[xml(namespace = ns::SASL_CERT, name = "items")]
94pub struct ListCertsResponse {
95 /// List of certificates.
96 #[xml(child(n = ..))]
97 pub items: Vec<Item>,
98}
99
100impl IqResultPayload for ListCertsResponse {}
101
102/// Client disables an X.509 certificate.
103#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
104#[xml(namespace = ns::SASL_CERT, name = "disable")]
105pub struct Disable {
106 /// Name of the certificate to disable.
107 #[xml(child)]
108 pub name: Name,
109}
110
111impl IqSetPayload for Disable {}
112
113/// Client revokes an X.509 certificate.
114#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
115#[xml(namespace = ns::SASL_CERT, name = "revoke")]
116pub struct Revoke {
117 /// Name of the certificate to revoke.
118 #[xml(child)]
119 pub name: Name,
120}
121
122impl IqSetPayload for Revoke {}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::ns;
128 use core::str::FromStr;
129 use minidom::Element;
130
131 #[cfg(target_pointer_width = "32")]
132 #[test]
133 fn test_size() {
134 assert_size!(Append, 28);
135 assert_size!(Disable, 12);
136 assert_size!(Revoke, 12);
137 assert_size!(ListCertsQuery, 0);
138 assert_size!(ListCertsResponse, 12);
139 assert_size!(Item, 40);
140 assert_size!(Resource, 12);
141 assert_size!(Users, 12);
142 assert_size!(Cert, 12);
143 }
144
145 #[cfg(target_pointer_width = "64")]
146 #[test]
147 fn test_size() {
148 assert_size!(Append, 56);
149 assert_size!(Disable, 24);
150 assert_size!(Revoke, 24);
151 assert_size!(ListCertsQuery, 0);
152 assert_size!(ListCertsResponse, 24);
153 assert_size!(Item, 80);
154 assert_size!(Resource, 24);
155 assert_size!(Users, 24);
156 assert_size!(Cert, 24);
157 }
158
159 #[test]
160 fn simple() {
161 let elem: Element = "<append xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name><x509cert>AAAA</x509cert></append>".parse().unwrap();
162 let append = Append::try_from(elem).unwrap();
163 assert_eq!(append.name.0, "Mobile Client");
164 assert_eq!(append.cert.data, b"\0\0\0");
165
166 let elem: Element =
167 "<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>"
168 .parse()
169 .unwrap();
170 let disable = Disable::try_from(elem).unwrap();
171 assert_eq!(disable.name.0, "Mobile Client");
172
173 let elem: Element =
174 "<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>"
175 .parse()
176 .unwrap();
177 let revoke = Revoke::try_from(elem).unwrap();
178 assert_eq!(revoke.name.0, "Mobile Client");
179 }
180
181 #[test]
182 fn list() {
183 let elem: Element = r#"<items xmlns='urn:xmpp:saslcert:1'>
184 <item>
185 <name>Mobile Client</name>
186 <x509cert>AAAA</x509cert>
187 <users>
188 <resource>Phone</resource>
189 </users>
190 </item>
191 <item>
192 <name>Laptop</name>
193 <x509cert>BBBB</x509cert>
194 </item>
195 </items>"#
196 .parse()
197 .unwrap();
198 let mut list = ListCertsResponse::try_from(elem).unwrap();
199 assert_eq!(list.items.len(), 2);
200
201 let item = list.items.pop().unwrap();
202 assert_eq!(item.name.0, "Laptop");
203 assert_eq!(item.cert.data, [4, 16, 65]);
204 assert!(item.users.is_none());
205
206 let item = list.items.pop().unwrap();
207 assert_eq!(item.name.0, "Mobile Client");
208 assert_eq!(item.cert.data, b"\0\0\0");
209 assert_eq!(item.users.unwrap().resources.len(), 1);
210 }
211
212 #[test]
213 fn test_serialise() {
214 let append = Append {
215 name: Name::from_str("Mobile Client").unwrap(),
216 cert: Cert {
217 data: b"\0\0\0".to_vec(),
218 },
219 no_cert_management: false,
220 };
221 let elem: Element = append.into();
222 assert!(elem.is("append", ns::SASL_CERT));
223
224 let disable = Disable {
225 name: Name::from_str("Mobile Client").unwrap(),
226 };
227 let elem: Element = disable.into();
228 assert!(elem.is("disable", ns::SASL_CERT));
229 let elem = elem.children().cloned().collect::<Vec<_>>().pop().unwrap();
230 assert!(elem.is("name", ns::SASL_CERT));
231 assert_eq!(elem.text(), "Mobile Client");
232 }
233
234 #[test]
235 fn test_serialize_item() {
236 let reference: Element = "<item xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name><x509cert>AAAA</x509cert></item>"
237 .parse()
238 .unwrap();
239
240 let item = Item {
241 name: Name::from_str("Mobile Client").unwrap(),
242 cert: Cert {
243 data: b"\0\0\0".to_vec(),
244 },
245 no_cert_management: false,
246 users: None,
247 };
248
249 let serialized: Element = item.into();
250 assert_eq!(serialized, reference);
251 }
252
253 #[test]
254 fn test_serialize_append() {
255 let reference: Element = "<append xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name><x509cert>AAAA</x509cert></append>"
256 .parse()
257 .unwrap();
258
259 let append = Append {
260 name: Name::from_str("Mobile Client").unwrap(),
261 cert: Cert {
262 data: b"\0\0\0".to_vec(),
263 },
264 no_cert_management: false,
265 };
266
267 let serialized: Element = append.into();
268 assert_eq!(serialized, reference);
269 }
270
271 #[test]
272 fn test_serialize_disable() {
273 let reference: Element =
274 "<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>"
275 .parse()
276 .unwrap();
277
278 let disable = Disable {
279 name: Name::from_str("Mobile Client").unwrap(),
280 };
281
282 let serialized: Element = disable.into();
283 assert_eq!(serialized, reference);
284 }
285
286 #[test]
287 fn test_serialize_revoke() {
288 let reference: Element =
289 "<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>"
290 .parse()
291 .unwrap();
292
293 let revoke = Revoke {
294 name: Name::from_str("Mobile Client").unwrap(),
295 };
296
297 let serialized: Element = revoke.into();
298 assert_eq!(serialized, reference);
299 }
300}