xmpp_parsers/
cert_management.rs

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}