Add a new client certificate management parser (XEP-0257).
This commit is contained in:
parent
d9a6aeea99
commit
46522ceb19
6 changed files with 230 additions and 0 deletions
|
@ -1,6 +1,7 @@
|
||||||
Version NEXT:
|
Version NEXT:
|
||||||
DATE Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
DATE Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
* New parsers/serialisers:
|
* New parsers/serialisers:
|
||||||
|
- Client Certificate Management for SASL EXTERNAL (XEP-0257)
|
||||||
- Client State Indication (XEP-0352)
|
- Client State Indication (XEP-0352)
|
||||||
- OpenPGP for XMPP (XEP-0373)
|
- OpenPGP for XMPP (XEP-0373)
|
||||||
- Anonymous unique occupant identifiers for MUCs (XEP-0421)
|
- Anonymous unique occupant identifiers for MUCs (XEP-0421)
|
||||||
|
|
8
doap.xml
8
doap.xml
|
@ -311,6 +311,14 @@
|
||||||
<xmpp:since>0.1.0</xmpp:since>
|
<xmpp:since>0.1.0</xmpp:since>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0257.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.3</xmpp:version>
|
||||||
|
<xmpp:since>NEXT</xmpp:since>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0260.html"/>
|
||||||
|
|
191
src/cert_management.rs
Normal file
191
src/cert_management.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
|
//
|
||||||
|
// 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<Vec<u8>>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
generate_element!(
|
||||||
|
/// For the client to upload an X.509 certificate.
|
||||||
|
Append, "append", SASL_CERT,
|
||||||
|
children: [
|
||||||
|
/// The name of this certificate.
|
||||||
|
name: Required<Name> = ("name", SASL_CERT) => Name,
|
||||||
|
|
||||||
|
/// The X.509 certificate to set.
|
||||||
|
cert: Required<Cert> = ("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> = ("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> = ("name", SASL_CERT) => Name,
|
||||||
|
|
||||||
|
/// The X.509 certificate to set.
|
||||||
|
cert: Required<Cert> = ("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> = ("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> = ("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> = ("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> = ("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 = "<append xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name><x509cert>AAAA</x509cert></append>".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 = "<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>".parse().unwrap();
|
||||||
|
let disable = Disable::try_from(elem).unwrap();
|
||||||
|
assert_eq!(disable.name.0, "Mobile Client");
|
||||||
|
|
||||||
|
let elem: Element = "<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>".parse().unwrap();
|
||||||
|
let revoke = Revoke::try_from(elem).unwrap();
|
||||||
|
assert_eq!(revoke.name.0, "Mobile Client");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list() {
|
||||||
|
let elem: Element = r#"
|
||||||
|
<items xmlns='urn:xmpp:saslcert:1'>
|
||||||
|
<item>
|
||||||
|
<name>Mobile Client</name>
|
||||||
|
<x509cert>AAAA</x509cert>
|
||||||
|
<users>
|
||||||
|
<resource>Phone</resource>
|
||||||
|
</users>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<name>Laptop</name>
|
||||||
|
<x509cert>BBBB</x509cert>
|
||||||
|
</item>
|
||||||
|
</items>"#.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::<Vec<_>>().pop().unwrap();
|
||||||
|
assert!(elem.is("name", ns::SASL_CERT));
|
||||||
|
assert_eq!(elem.text(), "Mobile Client");
|
||||||
|
}
|
||||||
|
}
|
|
@ -150,6 +150,9 @@ pub mod bob;
|
||||||
/// XEP-0234: Jingle File Transfer
|
/// XEP-0234: Jingle File Transfer
|
||||||
pub mod jingle_ft;
|
pub mod jingle_ft;
|
||||||
|
|
||||||
|
/// XEP-0257: Client Certificate Management for SASL EXTERNAL
|
||||||
|
pub mod cert_management;
|
||||||
|
|
||||||
/// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
|
/// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
|
||||||
pub mod jingle_s5b;
|
pub mod jingle_s5b;
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,9 @@ pub const JINGLE_FT: &str = "urn:xmpp:jingle:apps:file-transfer:5";
|
||||||
/// XEP-0234: Jingle File Transfer
|
/// XEP-0234: Jingle File Transfer
|
||||||
pub const JINGLE_FT_ERROR: &str = "urn:xmpp:jingle:apps:file-transfer:errors:0";
|
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
|
/// XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
|
||||||
pub const JINGLE_S5B: &str = "urn:xmpp:jingle:transports:s5b:1";
|
pub const JINGLE_S5B: &str = "urn:xmpp:jingle:transports:s5b:1";
|
||||||
|
|
||||||
|
|
|
@ -494,6 +494,9 @@ macro_rules! start_decl {
|
||||||
(Required, $type:ty) => (
|
(Required, $type:ty) => (
|
||||||
$type
|
$type
|
||||||
);
|
);
|
||||||
|
(Present, $type:ty) => (
|
||||||
|
bool
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! start_parse_elem {
|
macro_rules! start_parse_elem {
|
||||||
|
@ -506,6 +509,9 @@ macro_rules! start_parse_elem {
|
||||||
($temp:ident: Required) => {
|
($temp:ident: Required) => {
|
||||||
let mut $temp = None;
|
let mut $temp = None;
|
||||||
};
|
};
|
||||||
|
($temp:ident: Present) => {
|
||||||
|
let mut $temp = false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! do_parse {
|
macro_rules! do_parse {
|
||||||
|
@ -548,6 +554,18 @@ macro_rules! do_parse_elem {
|
||||||
}
|
}
|
||||||
$temp = Some(do_parse!($elem, $constructor));
|
$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 {
|
macro_rules! finish_parse_elem {
|
||||||
|
@ -566,6 +584,9 @@ macro_rules! finish_parse_elem {
|
||||||
" element."
|
" element."
|
||||||
)))?
|
)))?
|
||||||
};
|
};
|
||||||
|
($temp:ident: Present = $name:tt, $parent_name:tt) => {
|
||||||
|
$temp
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! generate_serialiser {
|
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:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append_all($parent.$elem.into_iter())
|
$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:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
|
||||||
$builder.append(::minidom::Node::Element(::minidom::Element::from($parent.$elem)))
|
$builder.append(::minidom::Node::Element(::minidom::Element::from($parent.$elem)))
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue