Skip to content

Commit

Permalink
Implement XEP-0380: Explicit Message Encryption
Browse files Browse the repository at this point in the history
This adds parsing and serialization for XEP-0380: Explicit Message
Encryption in version 0.3.0.

https://xmpp.org/extensions/xep-0380.html
  • Loading branch information
lnjX committed Sep 6, 2019
1 parent a092586 commit e7394af
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ QXmpp 1.0.1 (UNRELEASED)
New features:
- Add support for SCRAM-SHA-1 and SCRAM-SHA-256 (#183, @jlaine)
- Add XEP-0367: Message Attaching (v0.3.0) (#196, @lnjX)
- Add XEP-0380: Explicit Message Encryption (v0.3.0) (#199, @lnjX)
- Add XEP-0382: Spoiler messages (v0.2.0) (#195, @lnjX)

QXmpp 1.0.0 (Jan 8, 2019)
Expand Down
1 change: 1 addition & 0 deletions doc/xep.doc
Expand Up @@ -42,6 +42,7 @@ Complete:
- XEP-0319: Last User Interaction in Presence
- XEP-0352: Client State Indication
- XEP-0367: Message Attaching (v0.3.0)
- XEP-0380: Explicit Message Encryption (v0.3.0)
- XEP-0382: Spoiler messages (v0.2.0)

Ongoing:
Expand Down
10 changes: 10 additions & 0 deletions src/base/QXmppConstants.cpp
Expand Up @@ -37,6 +37,8 @@ const char* ns_stanza = "urn:ietf:params:xml:ns:xmpp-stanzas";
const char* ns_rpc = "jabber:iq:rpc";
// XEP-0020: Feature Negotiation
const char* ns_feature_negotiation = "http://jabber.org/protocol/feature-neg";
// XEP-0027: Current Jabber OpenPGP Usage
const char* ns_legacy_openpgp = "jabber:x:encrypted";
// XEP-0030: Service Discovery
const char* ns_disco_info = "http://jabber.org/protocol/disco#info";
const char* ns_disco_items = "http://jabber.org/protocol/disco#items";
Expand Down Expand Up @@ -135,6 +137,8 @@ const char* ns_chat_markers = "urn:xmpp:chat-markers:0";
const char* ns_csi = "urn:xmpp:csi:0";
// XEP-0363: HTTP File Upload
const char* ns_http_upload = "urn:xmpp:http:upload:0";
// XEP-0364: Current Off-the-Record Messaging Usage
const char* ns_otr = "urn:xmpp:otr:0";
// XEP-0367: Message Attaching
const char* ns_message_attaching = "urn:xmpp:message-attaching:1";
// XEP-0369: Mediated Information eXchange (MIX)
Expand All @@ -146,8 +150,14 @@ const char* ns_mix_node_participants = "urn:xmpp:mix:nodes:participants";
const char* ns_mix_node_presence = "urn:xmpp:mix:nodes:presence";
const char* ns_mix_node_config = "urn:xmpp:mix:nodes:config";
const char* ns_mix_node_info = "urn:xmpp:mix:nodes:info";
// XEP-0373: OpenPGP for XMPP
const char* ns_ox = "urn:xmpp:openpgp:0";
// XEP-0380: Explicit Message Encryption
const char* ns_eme = "urn:xmpp:eme:0";
// XEP-0382: Spoiler messages
const char* ns_spoiler = "urn:xmpp:spoiler:0";
// XEP-0384: OMEMO Encryption
const char* ns_omemo = "eu.siacs.conversations.axolotl";
// XEP-0405: Mediated Information eXchange (MIX): Participant Server Requirements
const char* ns_mix_pam = "urn:xmpp:mix:pam:1";
const char* ns_mix_roster = "urn:xmpp:mix:roster:0";
Expand Down
10 changes: 10 additions & 0 deletions src/base/QXmppConstants_p.h
Expand Up @@ -49,6 +49,8 @@ extern const char* ns_stanza;
extern const char* ns_rpc;
// XEP-0020: Feature Negotiation
extern const char* ns_feature_negotiation;
// XEP-0027: Current Jabber OpenPGP Usage
extern const char* ns_legacy_openpgp;
// XEP-0030: Service Discovery
extern const char* ns_disco_info;
extern const char* ns_disco_items;
Expand Down Expand Up @@ -147,6 +149,8 @@ extern const char* ns_chat_markers;
extern const char* ns_csi;
// XEP-0363: HTTP File Upload
extern const char* ns_http_upload;
// XEP-0364: Current Off-the-Record Messaging Usage
extern const char* ns_otr;
// XEP-0367: Message Attaching
extern const char* ns_message_attaching;
// XEP-0369: Mediated Information eXchange (MIX)
Expand All @@ -158,8 +162,14 @@ extern const char* ns_mix_node_participants;
extern const char* ns_mix_node_presence;
extern const char* ns_mix_node_config;
extern const char* ns_mix_node_info;
// XEP-0373: OpenPGP for XMPP
extern const char* ns_ox;
// XEP-0380: Explicit Message Encryption
extern const char* ns_eme;
// XEP-0382: Spoiler messages
extern const char* ns_spoiler;
// XEP-0384: OMEMO Encryption
extern const char* ns_omemo;
// XEP-0405: Mediated Information eXchange (MIX): Participant Server Requirements
extern const char* ns_mix_pam;
extern const char* ns_mix_roster;
Expand Down
105 changes: 104 additions & 1 deletion src/base/QXmppMessage.cpp
Expand Up @@ -55,6 +55,24 @@ static const char* marker_types[] = {
"acknowledged"
};

static const QStringList ENCRYPTION_NAMESPACES = {
QString(),
QString(),
ns_otr,
ns_legacy_openpgp,
ns_ox,
ns_omemo
};

static const QStringList ENCRYPTION_NAMES = {
QString(),
QString(),
QStringLiteral("OTR"),
QStringLiteral("Legacy OpenPGP"),
QStringLiteral("OpenPGP for XMPP (OX)"),
QStringLiteral("OMEMO")
};

static const char *ns_xhtml = "http://www.w3.org/1999/xhtml";

enum StampType
Expand Down Expand Up @@ -110,6 +128,10 @@ class QXmppMessagePrivate : public QSharedData
QString mixUserJid;
QString mixUserNick;

// XEP-0380: Explicit Message Encryption
QString encryptionMethod;
QString encryptionName;

// XEP-0382: Spoiler messages
bool isSpoiler = false;
QString spoilerHint;
Expand All @@ -123,7 +145,7 @@ class QXmppMessagePrivate : public QSharedData
/// \param thread

QXmppMessage::QXmppMessage(const QString& from, const QString& to, const
QString& body, const QString& thread)
QString& body, const QString& thread)
: QXmppStanza(from, to)
, d(new QXmppMessagePrivate)
{
Expand Down Expand Up @@ -571,6 +593,74 @@ void QXmppMessage::setMixUserNick(const QString& mixUserNick)
d->mixUserNick = mixUserNick;
}

/// Returns the encryption method this message is advertised to be encrypted
/// with.
///
/// \note QXmppMessage::NoEncryption does not necesserily mean that the message
/// is not encrypted; it may also be that the author of the message does not
/// support XEP-0380: Explicit Message Encryption.
///
/// \note If this returns QXmppMessage::UnknownEncryption, you can still get
/// the namespace of the encryption with \c encryptionMethodNs() and possibly
/// also a name with \c encryptionName().

QXmppMessage::EncryptionMethod QXmppMessage::encryptionMethod() const
{
if (d->encryptionMethod.isEmpty())
return QXmppMessage::NoEncryption;

int index = ENCRYPTION_NAMESPACES.indexOf(d->encryptionMethod);
if (index < 0)
return QXmppMessage::UnknownEncryption;
return static_cast<QXmppMessage::EncryptionMethod>(index);
}

/// Advertises that this message is encrypted with the given encryption method.
/// See XEP-0380: Explicit Message Encryption for details.

void QXmppMessage::setEncryptionMethod(QXmppMessage::EncryptionMethod method)
{
d->encryptionMethod = ENCRYPTION_NAMESPACES.at(int(method));
}

/// Returns the namespace of the advertised encryption method via. XEP-0380:
/// Explicit Message Encryption.

QString QXmppMessage::encryptionMethodNs() const
{
return d->encryptionMethod;
}

/// Sets the namespace of the encryption method this message advertises to be
/// encrypted with. See XEP-0380: Explicit Message Encryption for details.

void QXmppMessage::setEncryptionMethodNs(const QString &encryptionMethod)
{
d->encryptionMethod = encryptionMethod;
}

/// Returns the associated name of the encryption method this message
/// advertises to be encrypted with. See XEP-0380: Explicit Message Encryption
/// for details.

QString QXmppMessage::encryptionName() const
{
if (!d->encryptionName.isEmpty())
return d->encryptionName;
return ENCRYPTION_NAMES.at(int(encryptionMethod()));
}

/// Sets the name of the encryption method for XEP-0380: Explicit Message
/// Encryption.
///
/// \note This should only be used, if the encryption method is custom and is
/// not one of the methods listed in the XEP.

void QXmppMessage::setEncryptionName(const QString &encryptionName)
{
d->encryptionName = encryptionName;
}

/// Returns true, if this is a spoiler message according to XEP-0382: Spoiler
/// messages. The spoiler hint however can still be empty.
///
Expand Down Expand Up @@ -765,6 +855,10 @@ void QXmppMessage::parse(const QDomElement &element)
} else if (xElement.tagName() == "mix" && xElement.namespaceURI() == ns_mix) {
d->mixUserJid = xElement.firstChildElement("jid").text();
d->mixUserNick = xElement.firstChildElement("nick").text();
// XEP-0380: Explicit Message Encryption
} else if (xElement.tagName() == "encryption" && xElement.namespaceURI() == ns_eme) {
d->encryptionMethod = xElement.attribute("namespace");
d->encryptionName = xElement.attribute("name");
// XEP-0382: Spoiler messages
} else if (xElement.tagName() == "spoiler" && xElement.namespaceURI() == ns_spoiler) {
d->isSpoiler = true;
Expand Down Expand Up @@ -923,6 +1017,15 @@ void QXmppMessage::toXml(QXmlStreamWriter *xmlWriter) const
xmlWriter->writeEndElement();
}

// XEP-0380: Explicit Message Encryption
if (!d->encryptionMethod.isEmpty()) {
xmlWriter->writeStartElement("encryption");
xmlWriter->writeAttribute("xmlns", ns_eme);
xmlWriter->writeAttribute("namespace", d->encryptionMethod);
helperToXmlAddAttribute(xmlWriter, "name", d->encryptionName);
xmlWriter->writeEndElement();
}

// XEP-0382: Spoiler messages
if (d->isSpoiler) {
xmlWriter->writeStartElement("spoiler");
Expand Down
21 changes: 21 additions & 0 deletions src/base/QXmppMessage.h
Expand Up @@ -66,6 +66,18 @@ class QXMPP_EXPORT QXmppMessage : public QXmppStanza
Acknowledged
};

/// This enum describes different end-to-end encryption methods. These can
/// be used to mark a message explicitly as encrypted with a specific
/// algothim. See XEP-0380: Explicit Message Encryption for details.
enum EncryptionMethod {
NoEncryption, ///< No encryption
UnknownEncryption, ///< Unknown encryption
OTR, ///< XEP-0364: Current Off-the-Record Messaging Usage
LegacyOpenPGP, ///< XEP-0027: Current Jabber OpenPGP Usage
OX, ///< XEP-0373: OpenPGP for XMPP
OMEMO ///< XEP-0384: OMEMO Encryption
};

QXmppMessage(const QString& from = QString(), const QString& to = QString(),
const QString& body = QString(), const QString& thread = QString());

Expand Down Expand Up @@ -151,6 +163,15 @@ class QXMPP_EXPORT QXmppMessage : public QXmppStanza
QString mixUserNick() const;
void setMixUserNick(const QString&);

// XEP-0380: Explicit Message Encryption
EncryptionMethod encryptionMethod() const;
void setEncryptionMethod(EncryptionMethod);
QString encryptionMethodNs() const;
void setEncryptionMethodNs(const QString&);

QString encryptionName() const;
void setEncryptionName(const QString&);

// XEP-0382: Spoiler messages
bool isSpoiler() const;
void setIsSpoiler(bool);
Expand Down
46 changes: 46 additions & 0 deletions tests/qxmppmessage/tst_qxmppmessage.cpp
Expand Up @@ -50,6 +50,7 @@ private slots:
void testMessageCorrect();
void testMessageAttaching();
void testMix();
void testEme();
void testSpoiler();
};

Expand Down Expand Up @@ -114,6 +115,7 @@ void tst_QXmppMessage::testBasic()
QCOMPARE(message.isReceiptRequested(), false);
QCOMPARE(message.receiptId(), QString());
QCOMPARE(message.xhtml(), QString());
QCOMPARE(message.encryptionMethod(), QXmppMessage::NoEncryption);
QVERIFY(!message.isSpoiler());
serializePacket(message, xml);
}
Expand Down Expand Up @@ -663,6 +665,50 @@ void tst_QXmppMessage::testMix()
QCOMPARE(message.mixUserNick(), QString("erik"));
}

void tst_QXmppMessage::testEme()
{
// test standard encryption: OMEMO
const QByteArray xmlOmemo(
"<message to=\"foo@example.com/QXmpp\" from=\"bar@example.com/QXmpp\" type=\"normal\">"
"<body>This message is encrypted with OMEMO, but your client doesn't seem to support that.</body>"
"<encryption xmlns=\"urn:xmpp:eme:0\" namespace=\"eu.siacs.conversations.axolotl\"/>"
"</message>");

QXmppMessage messageOmemo;
parsePacket(messageOmemo, xmlOmemo);
QCOMPARE(messageOmemo.encryptionMethodNs(), QString("eu.siacs.conversations.axolotl"));
QCOMPARE(messageOmemo.encryptionMethod(), QXmppMessage::OMEMO);
QCOMPARE(messageOmemo.encryptionName(), QString("OMEMO"));
serializePacket(messageOmemo, xmlOmemo);

// test custom encryption
const QByteArray xmlCustom(
"<message to=\"foo@example.com/QXmpp\" from=\"bar@example.com/QXmpp\" type=\"normal\">"
"<body>This message is encrypted with CustomCrypt, but your client doesn't seem to support that.</body>"
"<encryption xmlns=\"urn:xmpp:eme:0\" namespace=\"im:example:customcrypt:1\" name=\"CustomCrypt\"/>"
"</message>");

QXmppMessage messageCustom;
parsePacket(messageCustom, xmlCustom);
QCOMPARE(messageCustom.encryptionMethodNs(), QString("im:example:customcrypt:1"));
QCOMPARE(messageCustom.encryptionMethod(), QXmppMessage::UnknownEncryption);
QCOMPARE(messageCustom.encryptionName(), QString("CustomCrypt"));
serializePacket(messageCustom, xmlCustom);

// test setters/getters
QXmppMessage message;
message.setEncryptionMethod(QXmppMessage::LegacyOpenPGP);
QCOMPARE(message.encryptionMethod(), QXmppMessage::LegacyOpenPGP);
QCOMPARE(message.encryptionMethodNs(), QString("jabber:x:encrypted"));
QCOMPARE(message.encryptionName(), QString("Legacy OpenPGP"));

message.setEncryptionMethodNs("fancyorg:encryption:fancycrypt:0");
message.setEncryptionName("FancyCrypt");
QCOMPARE(message.encryptionMethod(), QXmppMessage::UnknownEncryption);
QCOMPARE(message.encryptionMethodNs(), QString("fancyorg:encryption:fancycrypt:0"));
QCOMPARE(message.encryptionName(), QString("FancyCrypt"));
}

void tst_QXmppMessage::testSpoiler()
{
// test parsing with hint
Expand Down

0 comments on commit e7394af

Please sign in to comment.