mirror of
https://github.com/moparisthebest/xeps
synced 2024-11-27 11:42:17 -05:00
1030 lines
41 KiB
XML
1030 lines
41 KiB
XML
<?xml version='1.0' encoding='UTF-8'?>
|
|
<!DOCTYPE xep SYSTEM 'xep.dtd' [
|
|
<!ENTITY signcrypt "<signcrypt/>">
|
|
<!ENTITY sign "<sign/>">
|
|
<!ENTITY crypt "<crypt/>">
|
|
<!ENTITY openpgp "<openpgp/>">
|
|
<!ENTITY payload "<payload/>">
|
|
<!ENTITY % ents SYSTEM 'xep.ent'>
|
|
%ents;
|
|
]>
|
|
<?xml-stylesheet type='text/xsl' href='xep.xsl'?>
|
|
<xep>
|
|
<header>
|
|
<title>OpenPGP for XMPP</title>
|
|
<abstract>Specifies end-to-end encryption and authentication of data with the help of
|
|
OpenPGP, announcement, discovery and retrieval of public keys and a
|
|
mechanism to synchronize secret keys over multiple
|
|
devices.</abstract>
|
|
&LEGALNOTICE;
|
|
<number>0373</number>
|
|
<status>Experimental</status>
|
|
<type>Standards Track</type>
|
|
<sig>Standards</sig>
|
|
<approver>Council</approver>
|
|
<dependencies>
|
|
<spec>XMPP Core</spec>
|
|
<spec>XEP-0030</spec>
|
|
<spec>XEP-0082</spec>
|
|
<spec>XEP-0163</spec>
|
|
<spec>XEP-0223</spec>
|
|
<spec>XEP-0334</spec>
|
|
</dependencies>
|
|
<supersedes/>
|
|
<supersededby/>
|
|
<shortname>ox</shortname>
|
|
&flow;
|
|
<author>
|
|
<firstname>Dominik</firstname>
|
|
<surname>Schürmann</surname>
|
|
<email>dominik@dominikschuermann.de</email>
|
|
<jid>dominik@dominikschuermann.de</jid>
|
|
</author>
|
|
<author>
|
|
<firstname>Vincent</firstname>
|
|
<surname>Breitmoser</surname>
|
|
<email>look@my.amazin.horse</email>
|
|
<jid>valodim@stratum0.org</jid>
|
|
</author>
|
|
<revision>
|
|
<version>0.7.0</version>
|
|
<date>2021-05-04</date>
|
|
<initials>ps</initials>
|
|
<remark>
|
|
<p>Recommend PubSub access model 'open' for public key data node and metadata node.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.6.0</version>
|
|
<date>2020-11-22</date>
|
|
<initials>fs</initials>
|
|
<remark>
|
|
<p>Fix 'to'-attribute requirements: All content elements which are signed using OpenPGP need
|
|
that attribute to prevent Surreptitious Forward Attacks. The &crypt; element does not require
|
|
one, as the intented recipient is established by the encryption itself. The XEP had the
|
|
requirements for &sign; and &crypt; mixed up.</p>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.5.0</version>
|
|
<date>2020-06-19</date>
|
|
<initials>fs</initials>
|
|
<remark>
|
|
<ul>
|
|
<li>Use RFC 4880 terminology: it is "primary key", not "master key".</li>
|
|
<li>Clarify encryption of secret key material.</li>
|
|
<li>Move the information from the 'date' attribute into the item ID.</li>
|
|
</ul>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.4.0</version>
|
|
<date>2018-07-30</date>
|
|
<initials>ps</initials>
|
|
<remark>Fix node name in examples</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.3.3</version>
|
|
<date>2018-07-30</date>
|
|
<initials>ps</initials>
|
|
<remark>Improve note about OpenGPG fingerprint; editorial fixes</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.3.2</version>
|
|
<date>2018-07-05</date>
|
|
<initials>ps</initials>
|
|
<remark>Add example and small editorial fixes</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.3.1</version>
|
|
<date>2018-05-21</date>
|
|
<initials>ps</initials>
|
|
<remark>Fix slightly incorrect reference to RFC 4880</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.3.0</version>
|
|
<date>2018-04-16</date>
|
|
<initials>fs</initials>
|
|
<remark>
|
|
Split public keys into metadata and data nodes.
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.2.1</version>
|
|
<date>2017-11-13</date>
|
|
<initials>fs</initials>
|
|
<remark>
|
|
<ul>
|
|
<li>Recommend setting the PubSub configuration field 'send_last_published_item' to 'on_sub'.</li>
|
|
<li>Only recommend persistent PubSub nodes.</li>
|
|
</ul>
|
|
</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.2</version>
|
|
<date>2017-09-11</date>
|
|
<initials>XEP Editor (jwi)</initials>
|
|
<remark>Defer due to lack of activity.</remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.1.3</version>
|
|
<date>2016-07-15</date>
|
|
<initials>fs (XEP Editor: ssw)</initials>
|
|
<remark><p>Update acknowledgements.</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.1.2</version>
|
|
<date>2016-07-11</date>
|
|
<initials>bjc (XEP Editor: ssw)</initials>
|
|
<remark><p>Minior editorial fixes.</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.1.1</version>
|
|
<date>2016-06-04</date>
|
|
<initials>fs</initials>
|
|
<remark><p>Minior editorial fixes.</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.1</version>
|
|
<date>2016-05-10</date>
|
|
<initials>XEP Editor (ssw)</initials>
|
|
<remark><p>Initial published version approved by the XMPP Council.</p></remark>
|
|
</revision>
|
|
<revision>
|
|
<version>0.0.1</version>
|
|
<date>2016-03-25</date>
|
|
<initials>fs</initials>
|
|
<remark><p>First draft.</p></remark>
|
|
</revision>
|
|
</header>
|
|
|
|
<section1 topic='Introduction' anchor='intro'>
|
|
|
|
<p>This XMPP extension protocol specifies the foundations of
|
|
end-to-end encryption and authentication, based on digital
|
|
signatures, of data with the help of OpenPGP. Additional XEPs will
|
|
use this extension protocol as building block when specifying their
|
|
own OpenPGP profile suiting their use case. One such profile is the
|
|
Instant Messaging Profile specified in &xep0374;.</p>
|
|
|
|
<p>XMPP provides the mechanisms to solve a lot of issues that come
|
|
with modern day OpenPGP usage. For example, based on &xep0163; this
|
|
specification describes a standardized way to discover OpenPGP
|
|
public keys of other entities. But unlike the OpenPGP keyservers,
|
|
this process establishes a strong relation between the key and the
|
|
key's owning entity (usually a human user). A similar mechanism
|
|
described herein allows to synchronize the secret key(s) across
|
|
multiple devices.</p>
|
|
|
|
<p>OpenPGP in return allows for end-to-end encrypted data to be
|
|
exchanged between one, two or even multiple entities
|
|
(multi-end-to-multi-end encryption). Therefore this XEP can be used
|
|
for example to implement end-to-end encrypted &xep0045;.</p>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Glossary' anchor='glossary'>
|
|
|
|
<dl>
|
|
<di><dt>OpenPGP element</dt><dd>An XMPP extension element: &openpgp; qualified by the 'urn:xmpp:openpgp:0' namespace</dd></di>
|
|
<di><dt>OpenPGP content element</dt><dd>An element embedded via OpenPGP in a &openpgp; element. Either one of &signcrypt;, &sign; or &crypt;, qualified by the 'urn:xmpp:openpgp:0' namespace.</dd></di>
|
|
<di><dt>PEP</dt><dd>&xep0163;</dd></di>
|
|
<di><dt>Public-Key metadata node ("metadata node")</dt><dd>A PEP node containing metadata of the entity's public OpenPGP key.</dd></di>
|
|
<di><dt>Public-Key data node ("data node")</dt><dd>A PEP node containing an entity's public OpenPGP key.</dd></di>
|
|
<di><dt>Secret-Key node</dt><dd>A PEP node containing an entity's encrypted secret OpenPGP key.</dd></di>
|
|
<di><dt>OpenPGP v4 Fingerprint String</dt><dd>A String representing the OpenPGP v4 fingerprint
|
|
of a key. If the key consists of a primary key and subkeys, this is the fingerprint of the
|
|
primary key.</dd></di>
|
|
</dl>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='OpenPGP Encrypted and Signed Data' anchor='signcrypt'>
|
|
|
|
<section2 topic='Exchanging OpenPGP Encrypted and Signed Data' anchor='exchange'>
|
|
|
|
<p>The &openpgp; extension element qualified by the
|
|
'urn:xmpp:openpgp:0' namespace is used in order to exchange
|
|
encrypted and signed data.</p>
|
|
|
|
<example caption='The &openpgp; extension within a message.'><![CDATA[
|
|
<message to='juliet@example.org'>
|
|
<openpgp xmlns='urn:xmpp:openpgp:0'>
|
|
BASE64_OPENPGP_MESSAGE
|
|
</openpgp>
|
|
</message>]]></example>
|
|
|
|
<p>The text content of &openpgp; ("BASE64_OPENPGP_MESSAGE") is a
|
|
Base64 encoded (&rfc4648; <link
|
|
url='https://tools.ietf.org/html/rfc4648#section-4'>§ 4</link>)
|
|
OpenPGP message as specified in &rfc4880; which contains an
|
|
encrypted and/or signed UTF-8 (&rfc3629;) encoded string. This
|
|
string MUST correspond to exactly one OpenPGP content element,
|
|
that is, it represents either a &signcrypt;, a &sign; or a &crypt;
|
|
extension element qualified by the 'urn:xmpp:openpgp:0'
|
|
namespace. Note that OpenPGP's ASCII Armor is not used, instead
|
|
the XMPP client MUST encode the raw bytes of the OpenPGP message using
|
|
Base64.</p>
|
|
|
|
<p>In case of a &signcrypt; element, the OpenPGP message embedded
|
|
in the &openpgp; element MUST be encrypted and signed, and SHOULD
|
|
also be encrypted to self. In case of a &sign; element, the
|
|
OpenPGP message MUST be signed and MUST NOT be encrypted. In case
|
|
of &crypt; the OpenPGP message MUST NOT be signed, but MUST be
|
|
encrypted.</p>
|
|
|
|
<example caption='The &signcrypt; extension element.'><![CDATA[
|
|
<signcrypt xmlns='urn:xmpp:openpgp:0'>
|
|
<to jid='juliet@example.org'/>
|
|
<time stamp='2014-07-10T17:06:00+02:00'/>
|
|
<rpad>
|
|
f0rm1l4n4-mT8y33j!Y%fRSrcd^ZE4Q7VDt1L%WEgR!kv
|
|
</rpad>
|
|
<payload>
|
|
<body xmlns='jabber:client'>
|
|
This is a secret message.
|
|
</body>
|
|
</payload>
|
|
</signcrypt>]]></example>
|
|
|
|
<p>OpenPGP content elements MUST possess exactly one 'time'
|
|
element as direct child elements. The &signcrypt; and &sign;
|
|
content elements MUST contain at least one 'to' element(s), which
|
|
MUST have a 'jid' attribute containing the intended recipient's
|
|
XMPP address of the signed and/or encrypted data to prevent
|
|
Surreptitious Forward Attacks<note>Jee Hea An, Yevgeniy Dodis, and
|
|
Tal Rabin. 2002. On the Security of Joint Signature and
|
|
Encryption. In Proceedings of the International Conference on the
|
|
Theory and Applications of Cryptographic Techniques: Advances in
|
|
Cryptology (EUROCRYPT '02), Lars R. Knudsen
|
|
(Ed.). Springer-Verlag, London, UK, UK, 83-107. <<link
|
|
url='https://www.iacr.org/archive/eurocrypt2002/23320080/adr.pdf'>https://www.iacr.org/archive/eurocrypt2002/23320080/adr.pdf</link>></note>.
|
|
The XMPP address found in the 'to' element's 'jid' attribute
|
|
SHOULD be without Resourcepart (i.e., a bare JID). A &crypt; content
|
|
element may not carry a 'to' attribute. The 'time' element MUST
|
|
have a 'stamp' attribute which contains the timestamp when the
|
|
OpenPGP content element was signed and/or encrypted in the
|
|
DateTime format as specified in &xep0082; § 3.2. The &signcrypt;
|
|
and &crypt; elements SHOULD furthermore contain a 'rpad' element
|
|
which text content is a random-length random-content padding.</p>
|
|
|
|
<table caption='OpenPGP Content Element Properties'>
|
|
<tr>
|
|
<th>Content Element</th>
|
|
<th>'to' Element</th>
|
|
<th>'time' Element</th>
|
|
<th><rpad/> Element</th>
|
|
<th><payload/> Element</th>
|
|
</tr>
|
|
<tr>
|
|
<td>&signcrypt;</td>
|
|
<td>MUST have at least one</td>
|
|
<td>MUST have exactly one</td>
|
|
<td>SHOULD have exactly one</td>
|
|
<td>MUST have exactly one</td>
|
|
</tr>
|
|
<tr>
|
|
<td>&sign;</td>
|
|
<td>MUST have at least one</td>
|
|
<td>MUST have exactly one</td>
|
|
<td>OPTIONAL</td>
|
|
<td>MUST have exactly one</td>
|
|
</tr>
|
|
<tr>
|
|
<td>&crypt;</td>
|
|
<td>OPTIONAL</td>
|
|
<td>MUST have exactly one</td>
|
|
<td>SHOULD have exactly one</td>
|
|
<td>MUST have exactly one</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p>OpenPGP content elements MUST possess exactly one &payload;
|
|
element. The child elements of &payload; can be seen as OpenPGP
|
|
secured Stanza extension elements which are encrypted and/or
|
|
signed. After the &openpgp; element and the including &signcrypt;,
|
|
&sign; or &crypt; element was verified, they are processed
|
|
according to the specification of the relevant OpenPGP for XMPP
|
|
profile (see for example &xep0374;).</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Verification of &openpgp; Content' anchor='openpgp-verification'>
|
|
|
|
<p>Recipients MUST verify that the signature is valid, that the
|
|
signature's key corresponds to the sender's key, and that the
|
|
sender's key has a User ID containing the sender's XMPP
|
|
address in the form "xmpp:juliet@example.org" (for details see
|
|
<link url='#openpgp-user-ids'>"OpenPGP User IDs"</link>). Thus,
|
|
the recipient may
|
|
need to retrieve the key from the Personal Eventing Protocol node
|
|
as described above. At least one of the XMPP addresses found in
|
|
the 'to' elements contained in OpenPGP content element MUST
|
|
correspond to the outer 'to' of the XMPP &MESSAGE;. Furthermore,
|
|
recipients are RECOMMENDED to verify the 'time' element for
|
|
plausibility or to display it to a user for verification.</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Announcing and Discovering Public Keys via PEP' anchor='announcing-discover-pubkey'>
|
|
|
|
<p>Parties interested in exchanging encrypted data between each
|
|
other via OpenPGP need to know the public key(s) of the
|
|
recipients. The following section specifies a mechanism to announce
|
|
and discover public keys.</p>
|
|
|
|
<p>Two PEP node types are invovled: A "medatata node" is used to store meta information about
|
|
OpenPGP keys used by an entity while the actual public keys are stored in "data nodes".</p>
|
|
|
|
<section2 topic='The OpenPGP Public-Key Data Node' anchor='announcing-pubkey'>
|
|
|
|
<p>The public key data, as specified in <cite>RFC 4880</cite>, is stored in a PEP data
|
|
node. Note that OpenPGP's ASCII Armor is not used, instead the XMPP client MUST encode the
|
|
public key using Base64. The id of the node MUST be "urn:xmpp:openpgp:0:public-keys:" followed
|
|
by the fingerprint string of the OpenPGP public-key contained in the data node.</p>
|
|
|
|
<p>In absence of a use-case specific access model, it is RECOMMENDED to use the 'open' access
|
|
model for the public key data node in order to give entities without presence subscription
|
|
read access to the public key.</p>
|
|
|
|
<p>The access model can be changed efficiently by using publish-options.</p>
|
|
|
|
<p>The <em>OpenPGP v4 fingerprint string</em> is obtained as follows: First the raw bytes of the
|
|
fingerprint are computed as specified in <cite>RFC 4880 § 12.2.</cite>. Then the bytes are
|
|
encoded as a hexadecimal string using upper case characters<note>This matches the representation
|
|
used by GnuPG minus the SPACE separation.</note>.</p>
|
|
|
|
<p> The publishing entity SHOULD set the PubSub item ID to the time the item is published encoded
|
|
as DateTime format specified in <cite>XEP-0082</cite>.</p>
|
|
|
|
<p>The data node MUST contain an <pubkey/> element qualified by the 'urn:xmpp:openpgp:0'
|
|
namespace. The element MUST include a <data/> element which contains the data of the key
|
|
Base64 encoded.</p>
|
|
|
|
<example caption='Saving the public key in the data node.'><![CDATA[
|
|
<iq type='set' from='juliet@example.org/balcony' id='publish1'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<publish node='urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35'>
|
|
<item id='2020-01-21T10:46:21Z'>
|
|
<pubkey xmlns='urn:xmpp:openpgp:0'>
|
|
<data>
|
|
BASE64_OPENPGP_PUBLIC_KEY
|
|
</data>
|
|
</pubkey>
|
|
</item>
|
|
</publish>
|
|
<publish-options>
|
|
<x xmlns='jabber:x:data' type='submit'>
|
|
<field var='FORM_TYPE' type='hidden'>
|
|
<value>http://jabber.org/protocol/pubsub#publish-options</value>
|
|
</field>
|
|
<field var='pubsub#access_model'>
|
|
<value>open</value>
|
|
</field>
|
|
</x>
|
|
</publish-options>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='The OpenPGP Public Key Metadata Node' anchor='announcing-pubkey-list'>
|
|
|
|
<p>To update the public keys used by an entity, the metadata node is updated. Before adding a
|
|
OpenPGP key fingerprint to the metadata node, the publisher MUST ensure that the public key is available
|
|
at the corresponding data node.</p>
|
|
|
|
<p>Just like with the public key data node, in absence of a use-case specific access model,
|
|
it is RECOMMENDED to set the access model of the metadata node to 'open', such that entities
|
|
without mutual presence subscription are still able to access the node items.</p>
|
|
|
|
<p> The ID of the metadata node is 'urn:xmpp:openpgp:0:public-keys'. It contains a
|
|
<public-keys-list/> element qualified by the 'urn:xmpp:openpgp:0' namespace containing one
|
|
or more <pubkey-metadata/> elements. Every pubkey-metadata element MUST have a
|
|
'v4-fingerprint' attribute, containing the OpenPGP v4 fingerprint string, and a 'date'
|
|
attribute, containing the time the key was published or updated in DateTime format of
|
|
<cite>XEP-0082</cite>. An OpenPGP V4 fingerprint MUST NOT occur in the list more than once.</p>
|
|
|
|
<example caption='Publishing a public key to the metadata node.'><![CDATA[
|
|
<iq type='set' from='juliet@example.org/balcony' id='publish1'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<publish node='urn:xmpp:openpgp:0:public-keys'>
|
|
<item>
|
|
<public-keys-list xmlns='urn:xmpp:openpgp:0'>
|
|
<pubkey-metadata
|
|
v4-fingerprint='1357B01865B2503C18453D208CAC2A9678548E35'
|
|
date='2018-03-01T15:26:12Z'
|
|
/>
|
|
<pubkey-metadata
|
|
v4-fingerprint='67819B343B2AB70DED9320872C6464AF2A8E4C02'
|
|
date='1953-05-16T12:00:00Z'
|
|
/>
|
|
</public-keys-list>
|
|
</item>
|
|
</publish>
|
|
<publish-options>
|
|
<x xmlns='jabber:x:data' type='submit'>
|
|
<field var='FORM_TYPE' type='hidden'>
|
|
<value>http://jabber.org/protocol/pubsub#publish-options</value>
|
|
</field>
|
|
<field var='pubsub#access_model'>
|
|
<value>open</value>
|
|
</field>
|
|
</x>
|
|
</publish-options>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Discovering Public Keys of a User' anchor='discover-pubkey-list'>
|
|
|
|
<p>In order to discover the OpenPGP public keys of a remote entity, the interested entity first
|
|
queries the remote entity's metadata note to learn about the currently annouced OpenPGP
|
|
keys.</p>
|
|
|
|
<example caption='Requesting the metadata node of a user.'><![CDATA[
|
|
<iq from='romeo@example.org/orchard'
|
|
to='juliet@example.org'
|
|
type='get'
|
|
id='getmeta'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:public-keys'/>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
<example caption='Personal Eventing Protocol result containing the metadata node of the user.'><![CDATA[
|
|
<iq from='juliet@example.org'
|
|
to='romeo@example.org/orchard'
|
|
type='result'
|
|
id='getmeta'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:public-keys'>
|
|
<item>
|
|
<public-keys-list xmlns='urn:xmpp:openpgp:0'>
|
|
<pubkey-metadata
|
|
v4-fingerprint='1357B01865B2503C18453D208CAC2A9678548E35'
|
|
date='2018-03-01T15:26:12Z'
|
|
/>
|
|
<pubkey-metadata
|
|
v4-fingerprint='67819B343B2AB70DED9320872C6464AF2A8E4C02'
|
|
date='1953-05-16T12:00:00Z'
|
|
/>
|
|
</public-keys-list>
|
|
</item>
|
|
</items>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Requesting Public Keys' anchor='discover-pubkey'>
|
|
|
|
<p>OpenPGP key(s) can be retrieved by querying the data node for a specific
|
|
fingerprint.</p>
|
|
|
|
<example caption='Requesting an OpenPGP public key from an XMPP entity.'><![CDATA[
|
|
<iq from='romeo@example.org/orchard'
|
|
to='juliet@example.org'
|
|
type='get'
|
|
id='getpub'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35'
|
|
max_items='1'/>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
<example caption='Personal Eventing Protocol result containing the requested public key.'><![CDATA[
|
|
<iq from='juliet@example.org'
|
|
to='romeo@example.org/orchard'
|
|
type='result'
|
|
id='getpub'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35'>
|
|
<item id='2020-01-21T10:46:21Z'>
|
|
<pubkey xmlns='urn:xmpp:openpgp:0'>
|
|
<data>
|
|
BASE64_OPENPGP_PUBLIC_KEY
|
|
</data>
|
|
</pubkey>
|
|
</item>
|
|
</items>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
<p>Note that the result may contain multiple pubkey elements. Only
|
|
the public keys found in the most recent item MUST be used. Requesters
|
|
may want to limit the results to the most recent item using the
|
|
'max_items' attribute set to '1'. Clients could alternatively use
|
|
&xep0059; as an alternative to 'max_items' but accoding to
|
|
<cite>XEP-0060</cite> RSM is not (yet) mandatory for PubSub
|
|
services.</p>
|
|
|
|
<p>Some XMPP services may not provide the Personal Eventing
|
|
Protocol feature required to provide the mechanism described
|
|
here. If so, they will return an &IQ; error of type
|
|
service-unavailable.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Receiving notifications about key changes' anchor='pubsub-notifications'>
|
|
|
|
<p>Entities creating PEP nodes defined herein SHOULD configure the nodes as notification-only
|
|
nodes by setting 'pubsub#deliver_payloads" configuration field to 'false'.</p>
|
|
|
|
<p>Entities which are subscribed to the metadata node or advertise the
|
|
"urn:xmpp:openpgp:0:public-keys+notify" feature via &xep0030; (see <cite>XEP-0060</cite> <link
|
|
url='https://xmpp.org/extensions/xep-0060.html#filtered-notifications'>§ 9.2</link>) receive a
|
|
notification upon a node update. Entities subscribed to PEP nodes defined herein MUST be prepared
|
|
that PubSub notifications may be without the payload and only contain the published item's ID.</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Synchronizing the Secret Key with a Private PEP Node' anchor='synchro-pep'>
|
|
|
|
<!--
|
|
TODO: Also split in metadata and data node? Probably not, because it could cause stale secret
|
|
keys on the service (since it is not possible to list all PEP nodes starting with
|
|
e.g. urn:xmpp:openpgp:0:secret-keys and delete old ones. We could split later anyways.
|
|
-->
|
|
|
|
<p>A private PEP node is used to allow XMPP clients to synchronize
|
|
the user's secret OpenPGP key. Where private PEP node is defined: A
|
|
PEP node in whitelist mode where only the bare JID of the key
|
|
owner is whitelisted as described in &xep0223;. The secret key is
|
|
additionally encrypted.</p>
|
|
|
|
<section2 topic='Required PEP features' anchor='synchro-pep-requirements'>
|
|
|
|
<p>The used PEP server MUST support PEP and the whitelist access
|
|
model. It SHOULD also support persistent items.</p>
|
|
|
|
<section3 topic='Discovering support' anchor='synchro-pep-discover-support'>
|
|
|
|
<example caption='Account owner queries server regarding protocol support'><![CDATA[
|
|
<iq from='juliet@capulet.lit/balcony'
|
|
to='juliet@capulet.lit'
|
|
id='disco1'
|
|
type='get'>
|
|
<query xmlns='http://jabber.org/protocol/disco#info'/>
|
|
</iq>
|
|
]]></example>
|
|
|
|
<p>The service discovery result must contain a PEP identity
|
|
'<identity category='pubsub' type='pep'/>, and the
|
|
'http://jabber.org/protocol/pubsub#access-whitelist'
|
|
feature. Ideally it also contains the
|
|
'http://jabber.org/protocol/pubsub#persistent-items'
|
|
feature</p>
|
|
|
|
<example caption='Server communicates protocol support'><![CDATA[
|
|
<iq from='juliet@capulet.lit'
|
|
to='juliet@capulet.lit/balcony'
|
|
id='disco1'
|
|
type='result'>
|
|
<query xmlns='http://jabber.org/protocol/disco#info'>
|
|
<identity category='account' type='registered'/>
|
|
<identity category='pubsub' type='pep'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#access-presence'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#auto-create'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#auto-subscribe'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#config-node'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#create-and-configure'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#create-nodes'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#filtered-notifications'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#persistent-items'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#publish'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#retrieve-items'/>
|
|
<feature var='http://jabber.org/protocol/pubsub#subscribe'/>
|
|
...
|
|
</query>
|
|
</iq>]]></example>
|
|
|
|
</section3>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Requesting Information About the Secret Key PEP Node' anchor='req-info-secret-pep-node'>
|
|
|
|
<p>In order to synchronize the secret key over a private PEP node,
|
|
clients first need to discover and verify the node for the correct
|
|
settings.</p>
|
|
|
|
<section3 topic='Client Sends Request' anchor='client-sends-secret-request'>
|
|
|
|
<example caption='Requesting the user's secret key.'><![CDATA[
|
|
<iq from='romeo@example.org/orchard'
|
|
to='juliet@example.org'
|
|
type='get'
|
|
id='getsecret'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:secret-key'
|
|
max_items='1'/>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
</section3>
|
|
<section3 topic='PEP Service Success Response' anchor='client-receives-secret-response'>
|
|
|
|
<example caption='Personal Eventing Protocol result containing the requested secret key.'><![CDATA[
|
|
<iq from='juliet@example.org'
|
|
to='romeo@example.org/orchard'
|
|
type='result'
|
|
id='getsecret'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<items node='urn:xmpp:openpgp:0:secret-key'>
|
|
<item>
|
|
<secretkey xmlns='urn:xmpp:openpgp:0'>
|
|
BASE64_OPENPGP_ENCRYPTED_SECRET_KEY
|
|
</secretkey>
|
|
</item>
|
|
</items>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
|
|
</section3>
|
|
<section3 topic='PEP Node Does Not Exist Response' anchor='error-pep-node-inexistent'>
|
|
|
|
<p>If the node does not exist the service will return an &IQ;
|
|
error indicating the item-not-found error condition. The
|
|
client MUST then create it with an whitelist access model.</p>
|
|
|
|
<example caption='Node does not exist'><![CDATA[
|
|
<iq from='juliet@example.org'
|
|
to='romeo@example.org/orchard'
|
|
type='error'
|
|
id='getsecret'>
|
|
<error type='cancel'>
|
|
<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
|
</error>
|
|
</iq>]]></example>
|
|
|
|
</section3>
|
|
|
|
<section3 topic='PEP Not Supported' anchor='pep-not-supported'>
|
|
|
|
<p>The service will return a service-unavailable error &IQ; if
|
|
it does not support PEP.</p>
|
|
|
|
<example caption='Node does not exist'><![CDATA[
|
|
<iq from='juliet@example.org'
|
|
to='romeo@example.org/orchard'
|
|
type='error'
|
|
id='getsecret'>
|
|
<error type='cancel'>
|
|
<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
|
</error>
|
|
</iq>]]></example>
|
|
|
|
</section3>
|
|
|
|
</section2>
|
|
<section2 topic='Creating the Secret Key PEP Node' anchor='create-secret-node'>
|
|
|
|
<example caption='Client creates secret key PEP node'><![CDATA[
|
|
<iq type='set'
|
|
from='juliet@example.org/balcony'
|
|
id='create-node'>
|
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
<create node='urn:xmpp:openpgp:0:secret-key'/>
|
|
<configure>
|
|
<x xmlns='jabber:x:data' type='submit'>
|
|
<field var='FORM_TYPE' type='hidden'>
|
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
|
</field>
|
|
<field var='pubsub#access_model'>
|
|
<value>whitelist</value>
|
|
</field>
|
|
<field var='pubsub#send_last_published_item'>
|
|
<value>on_sub</value>
|
|
</field>
|
|
</x>
|
|
</configure>
|
|
</pubsub>
|
|
</iq>]]></example>
|
|
<example caption='Service informs requesting entity of success'><![CDATA[
|
|
<iq type='result'
|
|
to='juliet@example.org/balcony'
|
|
id='create-node'/>]]></example>
|
|
|
|
<p>The node is now created and the only affiliated entity is the
|
|
bare JID of the user, who created the node, with an affiliation as
|
|
'owner'.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Encrypting the Secret Key Backup' anchor='backup-encryption'>
|
|
|
|
<p>In order to set a new secret key, clients store the encrypted
|
|
secret key as Base64 encoded raw OpenPGP message within an
|
|
<secretkey/> element qualified by the 'urn:xmpp:openpgp:0'
|
|
namespace. These secret key backups are created as follows:</p>
|
|
|
|
<ol>
|
|
<li>All secret keys that should be included in the backup MUST
|
|
be concatenated in their transferable key format (<cite>RFC
|
|
4880</cite> <link
|
|
url='http://tools.ietf.org/html/rfc4880#section-11.2'>§
|
|
11.1</link>). The octet indicating string-to-key usage conventions
|
|
MUST be set to zero in the corresponding Secret-Key Packet(s)
|
|
(<cite>RFC 4880</cite> <link url='https://tools.ietf.org/html/rfc4880#section-5.5.3'>§ 5.5.3</link>).
|
|
The secret key material will be encrypted in step 4 using a
|
|
Symmetric-Key Encrypted Session Key Packet.
|
|
</li>
|
|
<li>A backup code is generated from secure random: The backup
|
|
code consists of 24 upper case characters from the Latin
|
|
alphabet and numbers without 'O' ("LATIN CAPITAL LETTER O")
|
|
and '0' ("DIGIT ZERO") (alphabet:
|
|
<tt>123456789ABCDEFGHIJKLMNPQRSTUVWXYZ</tt>) grouped into
|
|
4-character chunks, e.g.,
|
|
<tt>TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW</tt>. The characters MUST be
|
|
generated from cryptographically secure random. For example
|
|
<tt><link
|
|
url='https://lwn.net/Articles/606141/'>getrandom(2)</link></tt>,
|
|
<tt><link
|
|
url='https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html'>SecureRandom</link></tt>
|
|
or <tt>/dev/urandom</tt>. More information about the
|
|
randomness requirements for security can be found in &rfc4086;
|
|
</li>
|
|
<li>The whole backup code including the dashes is directly
|
|
used as a string to encrypt the concatenated transferable keys
|
|
as an OpenPGP message. More precisely: It is used as the
|
|
symmetric-key for a Symmetric-Key Encrypted Session Key Packet
|
|
according to <cite>RFC 4880</cite> <link
|
|
url='http://tools.ietf.org/html/rfc4880#section-5.3'>§
|
|
5.3</link>; the symmetric-key is thus 29 characters long
|
|
including the dashes. The encryption algorithm MUST be one of
|
|
the standardized OpenPGP symmetric algorithms, e.g, AES-128.
|
|
</li>
|
|
</ol>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Business Rules' anchor='rules'>
|
|
|
|
<section2 topic='OpenPGP Packet Format Version Restriction' anchor='openpgp-packet-format-version'>
|
|
|
|
<p>Implementations of this XEP MUST generate and accept only
|
|
version 4 (or higher) OpenPGP packets. Lower version OpenPGP
|
|
packets are insecure in many aspects (see for example <cite>RFC
|
|
4880</cite> <link
|
|
url='http://tools.ietf.org/html/rfc4880#section-5.5.2'>§
|
|
5.5.2</link>.).</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='PubSub Node Configuration' anchor='pubsub-node-configuration'>
|
|
|
|
<p>The Public-Key <em>metadata</em> node and the Secret-Key node SHOULD be configured to either
|
|
never send the latest item, or to send the latest item only when a new entity subscribed. Thus
|
|
the nodes 'send_last_published_item' configuration option SHOULD be set to either 'never' or
|
|
'on_sub' (see <cite>XEP-0060</cite> <link
|
|
url='https://xmpp.org/extensions/xep-0060.html#registrar-formtypes-config'>§ 16.4.4</link>).</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Key Enforcement' anchor='key-enforcement'>
|
|
|
|
<p>Whenever an entity becomes aware that the metadata node has changed (e.g., by receiving a PEP
|
|
update from their own account), it SHOULD check that the list contains the key they use. If the
|
|
key has been removed, the entity SHOULD reannounce it.</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Implementors Advice' anchor='implementors-advice'>
|
|
|
|
<section2 topic='Design Principles and Techniques' anchor='design-and-techniques'>
|
|
|
|
<p>OpenPGP implementations have a sad history of being not very
|
|
user-friendly which results in users either not using OpenPGP or in
|
|
users wrongly using OpenPGP. Implementors of this XEP, and
|
|
additional future XEPs based on this XEP, therefore should read
|
|
<span class='ref'><link
|
|
url='http://g10code.com/steed.html'>STEED</link></span><note>Koch,
|
|
Werner, and Marcus Brinkman "STEED — Usable End-to-End
|
|
Encryption", White Paper, g10 GmbH, 2011-10-17. <<link
|
|
url='http://g10code.com/steed.html'>http://g10code.com/steed.html</link>></note>
|
|
and <span class='ref'><link
|
|
url='https://www.cs.berkeley.edu/~tygar/papers/Why_Johnny_Cant_Encrypt/OReilly.pdf'>"Why
|
|
Johnny can't encrypt"</link></span><note>Whitten, Alma, and
|
|
J. Doug Tygar. "Why Johnny Can't Encrypt: A Usability Evaluation
|
|
of PGP 5.0." Usenix Security. Vol. 1999. 1999. <<link
|
|
url='https://www.cs.berkeley.edu/~tygar/papers/Why_Johnny_Cant_Encrypt/OReilly.pdf'>https://www.cs.berkeley.edu/~tygar/papers/Why_Johnny_Cant_Encrypt/OReilly.pdf</link>></note>. Implementors
|
|
of this XEP are encouraged to provide the concepts described in
|
|
STEED:</p>
|
|
|
|
<ul>
|
|
<li>Automatic key generation</li>
|
|
<li>Automatic key distribution</li>
|
|
<li>Opportunistic encryption</li>
|
|
<li>Trust upon first contact</li>
|
|
</ul>
|
|
|
|
<p>Furthermore implementors should design the user interface for
|
|
effective security by following the design principles and
|
|
techniques for security mentioned in "Why Johnny Can't
|
|
Encrypt".</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Stanza Size' anchor='stanza-size'>
|
|
|
|
<p>Implementors should be aware that the size OpenPGP public and
|
|
secret keys is somewhere in the range of tens of
|
|
kilobytes. Applying Base64 encoding on keys, as it is described
|
|
herein, further increases the size. The formula to determine the
|
|
Base64 encoded size is: ceil(bytes / 3) * 4. Thus the lower bound
|
|
for the maximum stanza size of 10000 bytes, as specified in <cite>RFC
|
|
6120</cite> § 13.12. 4., is usually exceeded. However all XMPP server
|
|
implementations, the authors are aware of, follow the
|
|
recommendation of the RFC and do not blindly set the maximum
|
|
stanza size to such a low value, but use a much higher
|
|
threshold. Therefore, this should hardly be an issue for
|
|
implementations. Nevertheless, it is advised to keep the size of
|
|
OpenPGP keys small by removing all signatures except the most
|
|
recent self-signature on each User ID before exporting the key
|
|
(cf. GnuPG's <tt>--export-options export-minimal</tt>).
|
|
In addition, implementors are advised to handle
|
|
<policy-violation/> error responses when trying to
|
|
transmit Base64 encoded keys.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='XMPP Address Normalization' anchor='xmpp-address-normalization'>
|
|
|
|
<p>The format of XMPP addresses, sometimes called JIDs, is well
|
|
defined. Thus they need to be normalized, as defined in
|
|
&rfc7622;. When implementations are required to compare XMPP
|
|
addresses for equality, as it is the case in <link
|
|
url='#openpgp-verification'>"Verification of &openpgp;
|
|
Content"</link>, then they also have to compare the normalized
|
|
versions of the addresses.</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Rationale' anchor='rationale'>
|
|
|
|
<section2 topic='Key Handling' anchor='key-handling'>
|
|
|
|
<p>This specification intentionally does not specify if the used
|
|
OpenPGP key should be a primary key or a subkey. It is even
|
|
possible to announce multiple public keys in the Personal Eventing
|
|
Protocol node. Implementations MUST be prepared to find multiple
|
|
public keys. The authors however believe that for ease of use only
|
|
one OpenPGP key specially crafted for the XMPP use case should be
|
|
created, announced and used.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='OpenPGP Element and Content Element Design' anchor='openpgp-element-design'>
|
|
|
|
<p>The &openpgp; and OpenPGP content elements are container
|
|
elements for arbitrary signed and encrypted data and can thus act
|
|
as building blocks for encrypted data included in Message, IQ and
|
|
Presence stanzas. For example, future specifications may use them
|
|
to implement encrypted versions of &xep0047; or &xep0261;.</p>
|
|
|
|
<p>Note that signed OpenPGP messages already contain a timestamp
|
|
as per the OpenPGP specification. OpenPGP content elements
|
|
nevertheless require the 'time' element because not every OpenPGP
|
|
API may provide access to the embedded OpenPGP timestamp.</p>
|
|
|
|
<p>The 'rpad' element of the OpenPGP content elements exists to
|
|
prevent length-based side channel attacks.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Addressing the Issues and Problems of XEP-0027' anchor='solving-xep0027-issues'>
|
|
|
|
<p>This specification addresses all relevant issues of &xep0027;
|
|
(<link url='https://xmpp.org/extensions/xep-0027.html#security'>§
|
|
4</link>, <link
|
|
url='https://xmpp.org/extensions/xep-0027.html#issues'>§
|
|
5</link>). It mitigates replay attacks by including the
|
|
recipient's address and a timestamp in the OpenPGP content
|
|
element<note>Full Replay attack prevention would require a
|
|
counter based approach.</note>. It allows for both, signing and
|
|
encrypting of the element. The scope of the specification was
|
|
deliberately limited to OpenPGP.</p>
|
|
|
|
<p>Features like signed presences, which is provided by <cite>XEP-0027</cite>,
|
|
may be added later on as add-on XEP to this.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='Not using OpenPGP ASCII Armor' anchor='openpgp-ascii-armor'>
|
|
|
|
<p>We decided against OpenPGP ASCII Armor (which contains an
|
|
additional checksum) and in favor for Base64, because
|
|
encoding should be part of the network application rather than the
|
|
crypto layer. Also XMPP, needs no additional error correction of payload.
|
|
In "MIME Security with OpenPGP" (&rfc3156;), ASCII Armor
|
|
has only been chosen to be backwards compatible with legacy applications
|
|
supporting non-MIME OpenPGP emails only.</p>
|
|
|
|
</section2>
|
|
|
|
<section2 topic='OpenPGP User IDs' anchor='openpgp-user-ids'>
|
|
|
|
<p>OpenPGP User IDs normally consist of a name - email address pair, e.g.,
|
|
"Juliet <juliet@example.org>" (<cite>RFC 4880</cite> <link
|
|
url='http://tools.ietf.org/html/rfc4880#section-5.11'>§ 5.11</link>).
|
|
For this XEP, we require User IDs of the format "xmpp:juliet@example.org".
|
|
First, it is required to have at least one User ID indicating the use
|
|
of this OpenPGP key. When doing certification of keys (key signing),
|
|
the partner must know what User ID she actually certifies.
|
|
Second, this format uses the standardized URI from <cite>XEP-0147</cite> to indicate
|
|
that this User ID corresponds to a key that is used for XMPP.
|
|
Third, having the Real Name inside provides no additional security
|
|
or guideline if this key should be certified. The XMPP address
|
|
is the only trust anchor here.</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Security Considerations' anchor='security'>
|
|
|
|
<p>The scope of this XEP is intentionally limited, so that the
|
|
specification just defines way for XMPP entities to discover,
|
|
announce and synchronize OpenPGP keys, and how to exchange signed
|
|
and encrypted data between two or more parties. Everything else is
|
|
outside its scope. For example, how 'secure' the key material is
|
|
protected on the endpoints is up to the implementation.</p>
|
|
|
|
<p>And while this XEP specifies a mechanism how to discover and
|
|
retrieve a public key, it does not define how the trust relation to
|
|
this key should be established. Even if key discovery and retrieval
|
|
over XMPP provides a stronger coupling between the possessing entity
|
|
(the XMPP address) and the key, as compared to the OpenPGP keyservers,
|
|
how a XMPP server authenticates a remote server is a server policy,
|
|
which does vary from server to server. Implementation MUST provide a
|
|
way for the user to establish and assign trust to a public key. For
|
|
example by using a QR code shown on the recipient's device screen.</p>
|
|
|
|
<p>Besides the protocol defined herein, OpenPGP implementations are
|
|
another big attack surface. Needless to say that the security of
|
|
encrypted data exchanged using this protocol depends on the security
|
|
of the used OpenPGP implementation. It is strongly RECOMMENED to use
|
|
existing implementations instead of writing your own. OpenPGP
|
|
implementations have suffered from various vulnerabilities in the past
|
|
which opened up DoS attack vectors. For example <link
|
|
url='https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-4402'>CVE-2013-4402</link>
|
|
and <link
|
|
url='https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-4617'>CVE-2014-4717</link>.</p>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='IANA Considerations' anchor='iana'>
|
|
|
|
<p>This document requires no interaction with &IANA;.</p>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='XMPP Registrar Considerations' anchor='registrar'>
|
|
|
|
<section2 topic='Protocol Namespaces' anchor='registrar-ns'>
|
|
|
|
<p>The ®ISTRAR; includes 'urn:xmpp:openpgp:0' in its registry of protocol namespaces (see &NAMESPACES;).</p>
|
|
|
|
</section2>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='XML Schema' anchor='schema'>
|
|
|
|
<p>TODO: Add after the XEP leaves the 'experimental' state.</p>
|
|
|
|
</section1>
|
|
|
|
<section1 topic='Acknowledgements' anchor='acknowledgements'>
|
|
|
|
<p>Thanks to Emmanuel Gil Peyrot, Sergei Golovan, Marc Laporte, Georg
|
|
Lukas, Adithya Abraham Philip, Brian Cully, fiaxh, Paul Schaub,
|
|
Philipp Hörist and Stefan Kropp for their feedback.</p>
|
|
|
|
<p>The first draft of this specification was worked out and written
|
|
on the wall of the 'Kymera' room in one of Google's buildings by the
|
|
authors, consisting of members of the XMPP Standards Foundation and
|
|
the OpenKeychain project, at the GSOC Mentors Summit 2015. The
|
|
authors would like to thank Google for making it possible by
|
|
bringing the right people together.</p>
|
|
|
|
</section1>
|
|
</xep>
|
|
|
|
<!-- Local Variables: -->
|
|
<!-- fill-column: 100 -->
|
|
<!-- indent-tabs-mode: nil -->
|
|
<!-- End: -->
|