diff --git a/ChangeLog b/ChangeLog index 275600e..f1f253d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ Version NEXT: DATE Emmanuel Gil Peyrot * New parsers/serialisers: + - Client Certificate Management for SASL EXTERNAL (XEP-0257) - Client State Indication (XEP-0352) - OpenPGP for XMPP (XEP-0373) - Anonymous unique occupant identifiers for MUCs (XEP-0421) diff --git a/doap.xml b/doap.xml index 6b8bf5a..b9fc996 100644 --- a/doap.xml +++ b/doap.xml @@ -311,6 +311,14 @@ 0.1.0 + + + + complete + 0.3 + NEXT + + diff --git a/src/cert_management.rs b/src/cert_management.rs new file mode 100644 index 0000000..a68909e --- /dev/null +++ b/src/cert_management.rs @@ -0,0 +1,191 @@ +// Copyright (c) 2019 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::iq::{IqSetPayload, IqGetPayload, IqResultPayload}; +use crate::util::helpers::Base64; + +generate_elem_id!( + /// The name of a certificate. + Name, "name", SASL_CERT +); + +generate_element!( + /// An X.509 certificate. + Cert, "x509cert", SASL_CERT, + text: ( + /// The BER X.509 data. + data: Base64> + ) +); + +generate_element!( + /// For the client to upload an X.509 certificate. + Append, "append", SASL_CERT, + children: [ + /// The name of this certificate. + name: Required = ("name", SASL_CERT) => Name, + + /// The X.509 certificate to set. + cert: Required = ("x509cert", SASL_CERT) => Cert, + + /// This client is forbidden from managing certificates. + no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool + ] +); + +impl IqSetPayload for Append {} + +generate_empty_element!( + /// Client requests the current list of X.509 certificates. + ListCertsQuery, "items", SASL_CERT +); + +impl IqGetPayload for ListCertsQuery {} + +generate_elem_id!( + /// One resource currently using a certificate. + Resource, "resource", SASL_CERT +); + +generate_element!( + /// A list of resources currently using this certificate. + Users, "users", SASL_CERT, + children: [ + /// Resources currently using this certificate. + resources: Vec = ("resource", SASL_CERT) => Resource + ] +); + +generate_element!( + /// An X.509 certificate being set for this user. + Item, "item", SASL_CERT, + children: [ + /// The name of this certificate. + name: Required = ("name", SASL_CERT) => Name, + + /// The X.509 certificate to set. + cert: Required = ("x509cert", SASL_CERT) => Cert, + + /// This client is forbidden from managing certificates. + no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool, + + /// List of resources currently using this certificate. + users: Option = ("users", SASL_CERT) => Users + ] +); + +generate_element!( + /// Server answers with the current list of X.509 certificates. + ListCertsResponse, "items", SASL_CERT, + children: [ + /// List of certificates. + items: Vec = ("item", SASL_CERT) => Item + ] +); + +impl IqResultPayload for ListCertsResponse {} + +generate_element!( + /// Client disables an X.509 certificate. + Disable, "disable", SASL_CERT, + children: [ + /// Name of the certificate to disable. + name: Required = ("name", SASL_CERT) => Name + ] +); + +impl IqSetPayload for Disable {} + +generate_element!( + /// Client revokes an X.509 certificate. + Revoke, "revoke", SASL_CERT, + children: [ + /// Name of the certificate to revoke. + name: Required = ("name", SASL_CERT) => Name + ] +); + +impl IqSetPayload for Revoke {} + +#[cfg(test)] +mod tests { + use super::*; + use minidom::Element; + use std::convert::TryFrom; + use std::str::FromStr; + use crate::ns; + + #[test] + fn test_size() { + assert_size!(Append, 56); + } + + #[test] + fn simple() { + let elem: Element = "Mobile ClientAAAA".parse().unwrap(); + let append = Append::try_from(elem).unwrap(); + assert_eq!(append.name.0, "Mobile Client"); + assert_eq!(append.cert.data, b"\0\0\0"); + + let elem: Element = "Mobile Client".parse().unwrap(); + let disable = Disable::try_from(elem).unwrap(); + assert_eq!(disable.name.0, "Mobile Client"); + + let elem: Element = "Mobile Client".parse().unwrap(); + let revoke = Revoke::try_from(elem).unwrap(); + assert_eq!(revoke.name.0, "Mobile Client"); + } + + #[test] + fn list() { + let elem: Element = r#" + + + Mobile Client + AAAA + + Phone + + + + Laptop + BBBB + + "#.parse().unwrap(); + let mut list = ListCertsResponse::try_from(elem).unwrap(); + assert_eq!(list.items.len(), 2); + + let item = list.items.pop().unwrap(); + assert_eq!(item.name.0, "Laptop"); + assert_eq!(item.cert.data, [4, 16, 65]); + assert!(item.users.is_none()); + + let item = list.items.pop().unwrap(); + assert_eq!(item.name.0, "Mobile Client"); + assert_eq!(item.cert.data, b"\0\0\0"); + assert_eq!(item.users.unwrap().resources.len(), 1); + } + + #[test] + fn test_serialise() { + let append = Append { + name: Name::from_str("Mobile Client").unwrap(), + cert: Cert { data: b"\0\0\0".to_vec() }, + no_cert_management: false, + }; + let elem: Element = append.into(); + assert!(elem.is("append", ns::SASL_CERT)); + + let disable = Disable { + name: Name::from_str("Mobile Client").unwrap(), + }; + let elem: Element = disable.into(); + assert!(elem.is("disable", ns::SASL_CERT)); + let elem = elem.children().cloned().collect::>().pop().unwrap(); + assert!(elem.is("name", ns::SASL_CERT)); + assert_eq!(elem.text(), "Mobile Client"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 06048c8..9f6053c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,6 +150,9 @@ pub mod bob; /// XEP-0234: Jingle File Transfer pub mod jingle_ft; +/// XEP-0257: Client Certificate Management for SASL EXTERNAL +pub mod cert_management; + /// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method pub mod jingle_s5b; diff --git a/src/ns.rs b/src/ns.rs index ef78508..328ed48 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -140,6 +140,9 @@ pub const JINGLE_FT: &str = "urn:xmpp:jingle:apps:file-transfer:5"; /// XEP-0234: Jingle File Transfer pub const JINGLE_FT_ERROR: &str = "urn:xmpp:jingle:apps:file-transfer:errors:0"; +/// XEP-0257: Client Certificate Management for SASL EXTERNAL +pub const SASL_CERT: &str = "urn:xmpp:saslcert:1"; + /// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method pub const JINGLE_S5B: &str = "urn:xmpp:jingle:transports:s5b:1"; diff --git a/src/util/macros.rs b/src/util/macros.rs index f5fda08..fd7f58c 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -494,6 +494,9 @@ macro_rules! start_decl { (Required, $type:ty) => ( $type ); + (Present, $type:ty) => ( + bool + ); } macro_rules! start_parse_elem { @@ -506,6 +509,9 @@ macro_rules! start_parse_elem { ($temp:ident: Required) => { let mut $temp = None; }; + ($temp:ident: Present) => { + let mut $temp = false; + }; } macro_rules! do_parse { @@ -548,6 +554,18 @@ macro_rules! do_parse_elem { } $temp = Some(do_parse!($elem, $constructor)); }; + ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { + if $temp { + return Err(crate::util::error::Error::ParseError(concat!( + "Element ", + $parent_name, + " must not have more than one ", + $name, + " child." + ))); + } + $temp = true; + }; } macro_rules! finish_parse_elem { @@ -566,6 +584,9 @@ macro_rules! finish_parse_elem { " element." )))? }; + ($temp:ident: Present = $name:tt, $parent_name:tt) => { + $temp + }; } macro_rules! generate_serialiser { @@ -595,6 +616,9 @@ macro_rules! generate_serialiser { ($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => { $builder.append_all($parent.$elem.into_iter()) }; + ($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => { + $builder.append(::minidom::Node::Element(::minidom::Element::builder($name).ns(crate::ns::$ns).build())) + }; ($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => { $builder.append(::minidom::Node::Element(::minidom::Element::from($parent.$elem))) };