xmpp-parsers: Implement XEP-0484: Fast Authentication Streamlining Tokens

Emmanuel Gil Peyrot created

This specification defines a token-based method to streamline
authentication in XMPP, allowing fully authenticated stream
establishment within a single round-trip.

Change summary

parsers/ChangeLog   |   2 
parsers/doap.xml    |   8 +++
parsers/src/fast.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++
parsers/src/lib.rs  |   3 +
parsers/src/ns.rs   |   3 +
5 files changed, 132 insertions(+)

Detailed changes

parsers/ChangeLog 🔗

@@ -1,5 +1,7 @@
 Version xxx:
 0000-00-00 Authors
+    * New parsers/serialisers:
+        - Fast Authentication Streamlining Tokens (XEP-0484)
     * Improvements:
       - Re-export the jid module entirely.
       - Add serde feature, passed to jid crate

parsers/doap.xml 🔗

@@ -608,6 +608,14 @@
             <xmpp:since>NEXT</xmpp:since>
         </xmpp:SupportedXep>
     </implements>
+    <implements>
+        <xmpp:SupportedXep>
+            <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0484.html"/>
+            <xmpp:status>complete</xmpp:status>
+            <xmpp:version>0.1.1</xmpp:version>
+            <xmpp:since>NEXT</xmpp:since>
+        </xmpp:SupportedXep>
+    </implements>
 
     <release>
         <Version>

parsers/src/fast.rs 🔗

@@ -0,0 +1,116 @@
+// Copyright (c) 2024 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 xso::{FromXml, IntoXml};
+
+use crate::date::DateTime;
+use crate::ns;
+
+generate_elem_id!(
+    /// A `<mechanism/>` element, describing one mechanism allowed by the server to authenticate.
+    Mechanism, "mechanism", FAST
+);
+
+// TODO: Replace this with a proper bool once we can derive FromXml and IntoXml on FastQuery.
+generate_attribute!(
+    /// Whether TLS zero-roundtrip is possible.
+    Tls0Rtt, "tls-0rtt", bool
+);
+
+generate_element!(
+/// This is the `<fast/>` element sent by the server as a SASL2 inline feature.
+FastQuery, "fast", FAST,
+attributes: [
+    /// Whether TLS zero-roundtrip is possible.
+    tls_0rtt: Default<Tls0Rtt> = "tls-0rtt",
+],
+children: [
+    /// A list of `<mechanism/>` elements, listing all server allowed mechanisms.
+    mechanisms: Vec<Mechanism> = ("mechanism", FAST) => Mechanism
+]
+);
+
+/// This is the `<fast/>` element the client MUST include within its SASL2 authentication request.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "fast")]
+pub struct FastResponse {
+    /// Servers MUST reject any authentication requests received via TLS 0-RTT payloads that do not
+    /// include a 'count' attribute, or where the count is less than or equal to a count that has
+    /// already been processed for this token.  This protects against replay attacks that 0-RTT is
+    /// susceptible to.
+    #[xml(attribute)]
+    pub count: u32,
+
+    /// If true and the client has successfully authenticated, the server MUST invalidate the
+    /// token.
+    #[xml(attribute(default))]
+    pub invalidate: bool,
+}
+
+/// This is the `<request-token/>` element sent by the client in the SASL2 authenticate step.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "request-token")]
+pub struct RequestToken {
+    /// This element MUST contain a 'mechanism' attribute, the value of which MUST be one of the
+    /// FAST mechanisms advertised by the server.
+    #[xml(attribute)]
+    pub mechanism: String,
+}
+
+/// This is the `<token/>` element sent by the server on successful SASL2 authentication containing
+/// a `<request-token/>` element.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "token")]
+pub struct Token {
+    /// The secret token to be used for subsequent authentications, as generated by the server.
+    #[xml(attribute)]
+    pub token: String,
+
+    /// The timestamp at which the token will expire.
+    #[xml(attribute)]
+    pub expiry: DateTime,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::Element;
+    use std::str::FromStr;
+
+    #[test]
+    fn test_simple() {
+        let elem: Element = "<fast xmlns='urn:xmpp:fast:0'><mechanism>FOO-BAR</mechanism></fast>"
+            .parse()
+            .unwrap();
+        let request = FastQuery::try_from(elem).unwrap();
+        assert_eq!(request.tls_0rtt, Tls0Rtt::False);
+        assert_eq!(request.mechanisms, [Mechanism(String::from("FOO-BAR"))]);
+
+        let elem: Element = "<fast xmlns='urn:xmpp:fast:0' count='123'/>"
+            .parse()
+            .unwrap();
+        let response = FastResponse::try_from(elem).unwrap();
+        assert_eq!(response.count, 123);
+        assert_eq!(response.invalidate, false);
+
+        let elem: Element = "<request-token xmlns='urn:xmpp:fast:0' mechanism='FOO-BAR'/>"
+            .parse()
+            .unwrap();
+        let request_token = RequestToken::try_from(elem).unwrap();
+        assert_eq!(request_token.mechanism, "FOO-BAR");
+
+        let elem: Element =
+            "<token xmlns='urn:xmpp:fast:0' token='ABCD' expiry='2024-06-30T17:13:57+02:00'/>"
+                .parse()
+                .unwrap();
+        let token = Token::try_from(elem).unwrap();
+        assert_eq!(token.token, "ABCD");
+        assert_eq!(
+            token.expiry,
+            DateTime::from_str("2024-06-30T17:13:57+02:00").unwrap()
+        );
+    }
+}

parsers/src/lib.rs 🔗

@@ -266,3 +266,6 @@ pub mod mam_prefs;
 
 /// XEP-0444: Message Reactions
 pub mod reactions;
+
+/// XEP-0484: Fast Authentication Streamlining Tokens
+pub mod fast;

parsers/src/ns.rs 🔗

@@ -302,3 +302,6 @@ pub const DEFAULT_NS: &str = JABBER_CLIENT;
 /// "jabber:component:accept" when the component feature is enabled.
 #[cfg(feature = "component")]
 pub const DEFAULT_NS: &str = COMPONENT_ACCEPT;
+
+/// XEP-0484: Fast Authentication Streamlining Tokens
+pub const FAST: &str = "urn:xmpp:fast:0";