<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE xep SYSTEM 'xep.dtd' [
  <!ENTITY envelope "&lt;envelope/&gt;">
  <!ENTITY payload "&lt;payload/&gt;">
  <!ENTITY % ents SYSTEM "xep.ent">
  <!ENTITY ns "urn:xmpp:ppqm:1">
  <!ENTITY nsdevices "urn:xmpp:ppqm:1:devices">
  <!ENTITY nsbundles "urn:xmpp:ppqm:1:bundles">
%ents;
]>
<?xml-stylesheet type='text/xsl' href='xep.xsl'?>
<xep>
<header>
  <title>PPQM Encryption</title>
  <abstract>This specification defines a protocol for post-quantum secure end-to-end encryption in one-to-one chats, as well as group chats where each participant may have multiple clients per account.</abstract>
  &LEGALNOTICE;
  <number>XXXX</number>
  <status>Experimental</status>
  <type>Standards Track</type>
  <sig>Standards</sig>
  <approver>Council</approver>
  <dependencies>
    <spec>XMPP Core</spec>
    <spec>XEP-0060</spec>
    <spec>XEP-0163</spec>
    <spec>XEP-0420</spec>
  </dependencies>
  <supersedes/>
  <supersededby/>
  <shortname>PPQM</shortname>
  <author>
    <firstname>Jamieson</firstname>
    <surname>O'Reilly</surname>
    <email>jamieson@privt.com</email>
  </author>
  <author>
    <firstname>Ron Lauren</firstname>
    <surname>Hombre</surname>
    <email>ronlauren@privt.com</email>
  </author>
  <author>
    <firstname>Hiren</firstname>
    <surname>Vavadiya</surname>
    <email>hiren@privt.com</email>
  </author>
  <revision>
    <version>0.1.0</version>
    <date>2024-08-18</date>
    <initials>jor</initials>
    <remark>
      <p>Initial version based on the OMEMO XEP with updates for post-quantum security using Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM).</p>
    </remark>
  </revision>
</header>

<section1 topic='Introduction' anchor='intro'>
  <section2 topic='Motivation' anchor='intro-motivation'>
    <p>
      The advancement of quantum computing poses a significant threat to classical encryption schemes currently in use. While protocols like Off-the-Record (OTR) messaging and OpenPGP have provided encryption in the past, they are not equipped to withstand the power of quantum computers. This necessitates a transition to post-quantum cryptographic algorithms.
    </p>
    <p>
      PPQM (Privt. Post-Quantum Multi-End Message and Object Encryption) is designed to address these emerging threats by incorporating post-quantum secure algorithms such as ML-KEM(CRYSTALS-Kyber). This ensures that communication remains secure even in the face of future quantum computing advancements.
    </p>
  </section2>
  <section2 topic='Overview' anchor='intro-overview'>
    <p>
      PPQM builds upon the strengths of the Signal protocol and the OMEMO protocol, but replaces the underlying cryptographic primitives with post-quantum secure algorithms. Specifically, PPQM utilizes ML-KEM for key encapsulation, ensuring that even if quantum computers become capable of breaking traditional encryption, PPQM-secured communications will remain confidential.
    </p>
    <p>
      Like OMEMO, PPQM supports multi-end encryption, allowing secure message synchronization across multiple devices. Each message is encrypted with a unique key, and headers containing the necessary decryption keys are individually encrypted for each recipient device. These headers and the encrypted payload are transmitted in a &lt;message&gt; stanza.
    </p>
    <p>
      PPQM maintains compatibility with existing XMPP standards by using extensions like XEP-0060 and XEP-0163 to manage and distribute key material securely.
    </p>
  </section2>
</section1>

<section1 topic='Requirements' anchor='reqs'>
  <p>As a result of XMPP's federated nature, messages may pass through multiple servers. It is crucial to secure communications from any intermediate hosts, especially in a post-quantum world. PPQM aims to provide robust end-to-end encryption that remains secure even against quantum-capable adversaries.</p>
  <p>PPQM provides the following guarantees under the threat model described in the next section:</p>
  <ul>
    <li>Confidentiality: Only the sender and receiver can read the content of a message, secured by post-quantum cryptographic primitives.</li>
    <li>Forward Secrecy: Compromised key material does not compromise previous message exchanges, ensured by the ML-KEM key encapsulation mechanism.</li>
    <li>Break-in Recovery: A session compromised due to key material leakage will recover after a few communication rounds.</li>
    <li>Authentication: Each peer can authenticate the sender or receiver of a message, leveraging post-quantum signatures.</li>
    <li>Integrity: Each peer can ensure that a message has not been altered by any intermediate node.</li>
    <li>Deniability: PPQM aims to provide deniability in a similar fashion to OMEMO, though post-quantum deniability is an area of active research.</li>
    <li>Asynchronicity: The protocol does not rely on the online status of any participant, ensuring usability in asynchronous messaging scenarios.</li>
  </ul>
  <p>PPQM is not intended to protect against the following scenarios:</p>
  <ul>
    <li>An attacker with permanent access to your device. In this case, the attacker could extract decrypted messages from the device.</li>
    <li>Loss of a device where an attacker can read messages on the notification screen.</li>
    <li>Denial-of-service attacks of any kind.</li>
  </ul>
  <p>
    Trust management remains a challenging topic, and is considered out of scope for this document.
  </p>
  <section2 topic='Threat Model' anchor='reqs-threat-model'>
    <p>The use case for PPQM is to protect the content of conversations against quantum-capable adversaries, including scenarios where servers cannot be trusted to maintain the confidentiality of the message content. This includes scenarios such as sharing sensitive corporate information or exchanging intimate messages that must remain confidential even from the server administrators.</p>
    <p>PPQM protects against passive and active attackers who can read, modify, replay, delay, and delete messages. However, PPQM does not protect against attacks based on metadata or traffic analysis.</p>
  </section2>
</section1>

<section1 topic='Glossary' anchor='glossary'>
  <dl>
    <di><dt>Device</dt><dd>A communication endpoint, i.e., a specific client instance.</dd></di>
    <di><dt>PPQM element</dt><dd>An &lt;encrypted&gt; element in the <tt>&ns;</tt> namespace.</dd></di>
    <di><dt>Bundle</dt><dd>A collection of publicly accessible data used by the post-quantum key agreement protocol, including the public IdentityKey, a list of single-use Curve25519 PreKeys, and a list of single-use KyberPreKeys with its corresponding signature.</dd></di>
    <di><dt>rid</dt><dd>The device id of the intended recipient of the containing &lt;key&gt;.</dd></di>
    <di><dt>sid</dt><dd>The device id of the sender of the containing PPQM element.</dd></di>
  </dl>
</section1>

<section1 topic='Protocol Definition' anchor='protocol'>
  <section2 topic='Overview' anchor='protocol-overview'>
    <p>
      This protocol utilizes a post-quantum adaptation of the Double Ratchet encryption scheme alongside a ML-KEM-based key agreement protocol. The following sections provide the technical details necessary to build an implementation of the PPQM protocol.
    </p>
  </section2>
  <section2 topic='Key Exchange' anchor='protocol-key_exchange'>
    <p>
      The key exchange in PPQM is based on a modified version of the X3DH key agreement protocol, updated to incorporate post-quantum secure components:
    </p>
    <dl>
      <di><dt>key encapsulation method</dt><dd>ML-KEM-1024</dd></di>
      <di><dt>hash function</dt><dd>SHA-3 (KMAC256)</dd></di>
      <di><dt>info string</dt><dd>&quot;PPQM X3DH&quot;</dd></di>
      <di><dt>signed KyberPreKey rotation period</dt><dd>Signed KyberPreKeys SHOULD be rotated periodically, from once a week to once a month.</dd></di>
      <di><dt>time to retain old private keys</dt><dd>The private key of the old signed KyberPreKey SHOULD be retained for an additional rotation period to accommodate delayed messages.</dd></di>
      <di><dt>number of KyberPreKeys to include in the bundle</dt><dd>The bundle SHOULD always contain around 100 KyberPreKeys.</dd></di>
      <di><dt>minimum number of KyberPreKeys</dt><dd>The bundle MUST contain at least 25 KyberPreKeys.</dd></di>
      <di><dt>usage of KyberPreKeys</dt><dd>All key exchanges MUST use a KyberPreKey; exchanges that do not use a KyberPreKey MUST be rejected.</dd></di>
      <di><dt>associated data</dt><dd>The associated data is created by concatenating the IdentityKeys of Alice and Bob: <tt>AD = Encode(IK_A) || Encode(IK_B)</tt>. <tt>Alice</tt> is the party that <em>actively initiated</em> the key exchange, while <tt>Bob</tt> is the party that <em>passively accepted</em> the key exchange.</dd></di>
      <di><dt>Signature method</dt><dd>Post-quantum secure signatures using ML-DSA(CRYSTALS-Dilithium) or similar.</dd></di>
    </dl>
    <p>
      The key exchange is performed just-in-time when sending the first message to a device, ensuring that each key exchange message also contains encrypted content as produced by the post-quantum Double Ratchet encryption scheme described below.
    </p>
  </section2>
  <section2 topic='Double Ratchet' anchor='protocol-double_ratchet'>
    <p>NOTE: <tt>PPQMMessage.proto</tt>, <tt>PPQMAuthenticatedMessage.proto</tt> and <tt>PPQMKeyExchange.proto</tt> refer to the protobuf structures as defined in the <link url="#protobuf-schema">Protobuf Schemas</link>.</p>
    <p>
      The Double Ratchet encryption scheme is adapted to use post-quantum key material. PPQM uses this protocol with the following parameters/settings:
    </p>
    <dl>
      <di><dt>ratchet initialization</dt><dd>
        The Double Ratchet is initialized using the shared secret, associated data, and public keys as yielded by the post-quantum key agreement protocol, as explained in the Double Ratchet specification. The ephemeral key pair (ek) used by the key agreement protocol is stored with the session.
      </dd></di>
      <di><dt>MAX_SKIP</dt><dd>As with OMEMO, storing skipped message keys can introduce potential DoS attacks. It is RECOMMENDED to limit the storage to 1000 skipped message keys per session.</dd></di>
      <di><dt>deletion policy for skipped message keys</dt><dd>PPQM follows the same guidelines as OMEMO, with skipped message keys being discarded on a FIFO basis when the limit is reached.</dd></di>
      <di><dt>authentication tag truncation</dt><dd>Authentication tags are truncated to 16 bytes/128 bits.</dd></di>
      <di><dt>CONCAT(ad, header)</dt><dd><tt>CONCAT(ad, header) = ad || PPQMMessage.proto(header)</tt></dd></di>
      <di><dt>KDF_RK(rk, dh_out)</dt><dd>KMAC256 using the root key (rk) as HKDF salt, the output of a Diffie-Hellman (dh_out) as HKDF input material, and &quot;PPQM Root Chain&quot; as HKDF info.</dd></di>
      <di><dt>KDF_CK(ck)</dt><dd>KMAC256 using a chain key (ck) as the HMAC key, with specified constants for message key and chain key derivation.</dd></di>
      <di><dt>ENCRYPT(mk, plaintext, associated_data)</dt><dd>
        The encryption step uses authenticated encryption consisting of AES-256-GCM.
        <ol>
          <li>Use KMAC256 to generate key material from the message key (mk).</li>
          <li>Encrypt the plaintext using AES-256-GCM with the derived key.</li>
          <li>Authenticate the ciphertext using associated data.</li>
        </ol>
      </dd></di>
    </dl>
    <p>Further information on these functions can be found in the Double Ratchet specification.</p>
    <p>
      If the message requires a key exchange, the key exchange data is placed into a new <tt>PPQMKeyExchange.proto</tt> structure together with the <tt>PPQMAuthenticatedMessage.proto</tt> structure.
    </p>
    <p>
      To account for lost and out-of-order messages, <tt>PPQMKeyExchange.proto</tt> structures are sent until a response by the recipient confirms successful key exchange. Subsequent messages use the stored header until confirmation is received.
    </p>
  </section2>
  <section2 topic='Message Encryption' anchor='protocol-message_encryption'>
    <p>
      The contents are encrypted and authenticated using a combination of AES-256-GCM with SHA-3(KMAC256) for key derivation.
    </p>
    <ol>
      <li>Generate 32 bytes of cryptographically secure random data, called <tt>key</tt>.</li>
      <li>Use KMAC256 to generate key material from the key.</li>
      <li>Encrypt the plaintext using AES-256-GCM with the derived key.</li>
      <li>Concatenate the key and authentication data, then encrypt them using the Double Ratchet.</li>
    </ol>
  </section2>
  <section2 topic='Message Decryption' anchor='protocol-message_decryption'>
    <p>
      The contents are decrypted by reversing the encryption steps.
    </p>
    <ol>
      <li>Decrypt the key and authentication data from the PPQMKeyExchange or PPQMAuthenticatedMessage.</li>
      <li>Use KMAC256 to regenerate the encryption key.</li>
      <li>Verify the authentication data and decrypt the ciphertext using AES-256-GCM.</li>
    </ol>
  </section2>
</section1>

<section1 topic='Use Cases' anchor='usecases'>
  <section2 topic='Setup' anchor='usecases-setup'>
    <p>
      To participate in PPQM-encrypted chats, clients need to set up a PPQM library and generate a device id, which is a randomly generated integer between 1 and 2^31 - 1. The device id must be unique for the account.
    </p>
  </section2>
  <section2 topic='Discovering peer support' anchor='usecases-discovering'>
    <p>To determine whether a given contact supports PPQM, the devices node in PEP is consulted. Devices MUST subscribe to <tt>&nsdevices;</tt> via PEP to stay informed of new devices.</p>
  </section2>
  <section2 topic='Announcing support' anchor='usecases-announcing'>
    <section3 topic='Device list' anchor='devices'>
    <p>A device must announce itself by adding its device id to the devices PEP node. The node’s access model must be set to ‘open’ for this purpose.</p>
    </section3>
    <section3 topic='Bundles' anchor='bundles'>
    <p>A device must publish its IdentityKey, a Signed PreKey, a list of PreKeys, and a list of KyberPreKeys in a bundle. This is stored in the <tt>&nsbundles;</tt> node, which must support multiple items.</p>
    </section3>
  </section2>
  <section2 topic='Building a session' anchor='usecases-building'>
    <p>To build a session with a device, fetch its bundle information and use a random PreKey and KyberPreKey to establish a PPQM session.</p>
  </section2>
  <section2 topic='Sending a message' anchor='usecases-messagesend'>
    <section3 topic='SCE Profile' anchor='sce'>
      <p>
        A PPQM SCE &envelope; element MUST contain an &lt;rpad/&gt; affix element. Other optional elements include &lt;time/&gt; and &lt;from/&gt;. 
      </p>
    </section3>
    <section3 topic='Encryption' anchor='encrypt'>
      <p>
        The &envelope; element is encrypted as described in the Message Encryption section. 
      </p>
    </section3>
    <section3 topic='Message structure description' anchor='message-structure-description'>
      <p>
        A PPQM encrypted message includes an &lt;encrypted&gt; element in the <tt>&ns;</tt> namespace. It contains a &lt;header&gt; and a &payload; element.
      </p>
      <p>Empty PPQM messages omit the &lt;payload&gt; element, containing only the &lt;header&gt;.</p>
    </section3>
  </section2>
  <section2 topic='Receiving a message' anchor='usecases-receiving'>
    <p>Upon receiving a PPQM element, check if it contains a &lt;keys&gt; element for your device. If so, proceed with decryption or session establishment as necessary.</p>
  </section2>
  <section2 topic='Opt-out' anchor='opt-out'>
    <p>An account can signal its desire to stop PPQM communication by sending an &lt;opt-out/&gt; element qualified by the <tt>&ns;</tt> namespace.</p>
  </section2>
  <section2 topic='Group Chats' anchor='group-chats'>
    <section3 topic='Retrieving and maintaining members list' anchor='members-list'>
    <p>Retrieve the members list when joining a group chat to determine recipients of PPQM messages.</p>
    </section3>
    <section3 topic='Fetching devices and bundles' anchor='group-fetch'>
    <p>Fetch device lists and bundles for each member before sending a message.</p>
    </section3>
    <section3 topic='Sending a message' anchor='group-send'>
    <p>PPQM group messages are similar to 1:1 messages but include multiple &lt;keys&gt; elements for each participant.</p>
    </section3>
  </section2>
</section1>

<section1 topic='Business Rules' anchor='rules'>
  <p>Before publishing a new device id, ensure it is unique. Sessions should be constructed just before sending a message to avoid key collisions.</p>
  <p>After receiving a PPQMKeyExchange and successfully building a session, respond with an empty PPQM message to notify the sender.</p>
  <p>Handle decryption failures with care, ignoring repeated messages but notifying users of other failures.</p>
  <p>Postpone private key deletion until message catch-up is complete. Manually replace broken sessions as needed.</p>
</section1>

<section1 topic='Implementation Notes' anchor='impl'>
  <section2 topic='Server side requirements' anchor='server-side'>
    <p>PPQM requires a Pubsub Service that supports persistent items, publish-options, 'max' items, and 'open' access models.</p>
  </section2>
</section1>

<section1 topic='Security Considerations' anchor='security'>
  <p>Clients must not use a newly built session without user intervention. Trust decisions should involve fingerprint verification. Avoid automatic session creation following decryption errors.</p>
</section1>

<section1 topic='IANA Considerations' anchor='iana'>
  <p>This document requires no interaction with the Internet Assigned Numbers Authority (IANA).</p>
</section1>

<section1 topic='XMPP Registrar Considerations' anchor='registrar'>
  <section2 topic='Protocol Namespaces' anchor='namespaces'>
    <p>This specification defines the following XMPP namespaces:</p>
    <ul>
      <li>&ns;</li>
    </ul>
  </section2>
  <section2 topic='Protocol Versioning' anchor='versioning'>
    &NSVER;
  </section2>
</section1>

<section1 topic='XML Schema' anchor='schema'>
  <code><![CDATA[
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
           targetNamespace=']]>&ns;<![CDATA['
           xmlns=']]>&ns;<![CDATA['>

    <xs:element name='encrypted'>
        <xs:complexType>
            <xs:all>
                <xs:element ref='header'/>
                <xs:element ref='payload' minOccurs='0' maxOccurs='1'/>
            </xs:all>
        </xs:complexType>
    </xs:element>

    <xs:element name='payload' type='xs:base64Binary'/>

    <xs:element name='header'>
        <xs:complexType>
            <xs:sequence maxOccurs='unbounded'>
                <xs:element ref='keys'/>
            </xs:sequence>
            <xs:attribute name='sid' type='xs:unsignedInt'/>
        </xs:complexType>
    </xs:element>

    <xs:element name='keys'>
        <xs:complexType>
            <xs:sequence maxOccurs='unbounded'>
                <xs:element ref='key'/>
            </xs:sequence>
            <xs:attribute name='jid' type='xs:string' use='required'/>
        </xs:complexType>
    </xs:element>

    <xs:element name='key'>
        <xs:complexType>
            <xs:simpleContent>
                <xs:extension base='xs:base64Binary'>
                    <xs:attribute name='rid' type='xs:unsignedInt' use='required'/>
                    <xs:attribute name='kex' type='xs:boolean' default='false'/>
                </xs:extension>
            </xs:simpleContent>
        </xs:complexType>
    </xs:element>

    <xs:element name='devices'>
        <xs:complexType>
            <xs:sequence maxOccurs='unbounded'>
                <xs:element ref='device'/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name='device'>
        <xs:complexType>
            <xs:attribute name='id' type='xs:unsignedInt' use='required'/>
            <xs:attribute name='label' type='xs:string'/>
        </xs:complexType>
    </xs:element>

    <xs:element name='bundle'>
        <xs:complexType>
            <xs:all>
                <xs:element ref='spk'/>
                <xs:element ref='spks'/>
                <xs:element ref='ik'/>
                <xs:element ref='prekeys'/>
                <xs:element ref='kemprekeys'/>
            </xs:all>
        </xs:complexType>
    </xs:element>

    <xs:element name='spk'>
        <xs:complexType>
            <xs:simpleContent>
                <xs:extension base='xs:base64Binary'>
                    <xs:attribute name='id' type='xs:unsignedInt' use='required'/>
                </xs:extension>
            </xs:simpleContent>
        </xs:complexType>
    </xs:element>

    <xs:element name='spks' type='xs:base64Binary'/>
    <xs:element name='ik' type='xs:base64Binary'/>

    <xs:element name='prekeys'>
        <xs:complexType>
            <xs:sequence maxOccurs='unbounded'>
                <xs:element ref='pk'/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name='pk'>
        <xs:complexType>
            <xs:simpleContent>
                <xs:extension base='xs:base64Binary'>
                    <xs:attribute name='id' type='xs:unsignedInt' use='required'/>
                </xs:extension>
            </xs:simpleContent>
        </xs:complexType>
    </xs:element>
    <xs:element name='kemprekeys'>
        <xs:complexType>
            <xs:sequence minOccurs='25'>
                <xs:element ref='kpk'/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name='kpk'>
        <xs:complexType>
            <xs:simpleContent>
                <xs:extension base='xs:base64Binary'>
                    <xs:attribute name='id' type='xs:unsignedInt' use='required'/>
                    <xs:attribute name='sig' type='xs:base64Binary' use='required'/>
                </xs:extension>
            </xs:simpleContent>
        </xs:complexType>
    </xs:element>
</xs:schema>
]]></code>
</section1>

<section1 topic='Protobuf Schema' anchor='protobuf-schema'>
  <code><![CDATA[
message PPQMMessage {
    required uint32 n          = 1;
    required uint32 pn         = 2;
    required bytes  dh_pub     = 3;
    optional bytes  ciphertext = 4;
}

message PPQMAuthenticatedMessage {
    required bytes mac     = 1;
    required bytes message = 2; // Byte-encoding of a PPQMMessage
}

message PPQMKeyExchange {
    required uint32 pk_id  = 1;
    required uint32 spk_id = 2;
    required bytes  ik     = 3;
    required bytes  ek     = 4;
    required PPQMAuthenticatedMessage message = 5;
}
]]></code>
</section1>

<section1 topic='Acknowledgements' anchor='ack'>
  <p>Big thanks to Daniel Gultsch for mentoring the original development of OMEMO. Thanks to the PPQM development team for bringing post-quantum security to XMPP. Special thanks to the community for their continued support and feedback.</p>
</section1>
</xep>
