aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2015-08-28 14:17:16 +0200
committerJeremy Lainé <jeremy.laine@m4x.org>2015-08-28 14:17:16 +0200
commit721836a76a028982495ccab6758a2d34a149fff3 (patch)
treeb09f1ca850b3780b617c968f82e18846ef26baf6
parentb447fb1a91b0e4e5c46700c123aa8d79ed711891 (diff)
downloadqxmpp-721836a76a028982495ccab6758a2d34a149fff3.tar.gz
parse DTLS-SRTP attributes according to XEP-0320
-rw-r--r--src/base/QXmppJingleIq.cpp114
-rw-r--r--src/base/QXmppJingleIq.h10
-rw-r--r--tests/qxmppjingleiq/tst_qxmppjingleiq.cpp132
3 files changed, 227 insertions, 29 deletions
diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp
index a9371661..26799054 100644
--- a/src/base/QXmppJingleIq.cpp
+++ b/src/base/QXmppJingleIq.cpp
@@ -33,6 +33,7 @@
static const int RTP_COMPONENT = 1;
static const char* ns_jingle_rtp_info = "urn:xmpp:jingle:apps:rtp:info:1";
+static const char* ns_jingle_dtls = "urn:xmpp:jingle:apps:dtls:0";
static const char* jingle_actions[] = {
"content-accept",
@@ -73,6 +74,25 @@ static const char* jingle_reasons[] = {
"unsupported-transports",
};
+static QString formatFingerprint(const QByteArray &digest)
+{
+ QString fingerprint;
+ const QString hx = digest.toHex().toUpper();
+ for (int i = 0; i < hx.size(); i+=2) {
+ if (!fingerprint.isEmpty())
+ fingerprint += ':';
+ fingerprint += hx.mid(i, 2);
+ }
+ return fingerprint;
+}
+
+static QByteArray parseFingerprint(const QString &fingerprint)
+{
+ QString z = fingerprint;
+ z.replace(':', "");
+ return QByteArray::fromHex(z.toUtf8());
+}
+
static QString addressToSdp(const QHostAddress &host)
{
return QString("IN %1 %2").arg(
@@ -141,6 +161,11 @@ public:
QString transportType;
QString transportUser;
QString transportPassword;
+
+ QByteArray transportFingerprint;
+ QString transportFingerprintHash;
+ QString transportFingerprintSetup;
+
QList<QXmppJinglePayloadType> payloadTypes;
QList<QXmppJingleCandidate> transportCandidates;
};
@@ -262,6 +287,60 @@ void QXmppJingleIq::Content::setTransportPassword(const QString &password)
d->transportPassword = password;
}
+/// Returns the fingerprint hash value for the transport key.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+QByteArray QXmppJingleIq::Content::transportFingerprint() const
+{
+ return d->transportFingerprint;
+}
+
+/// Sets the fingerprint hash value for the transport key.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+void QXmppJingleIq::Content::setTransportFingerprint(const QByteArray &fingerprint)
+{
+ d->transportFingerprint = fingerprint;
+}
+
+/// Returns the fingerprint hash algorithm for the transport key.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+QString QXmppJingleIq::Content::transportFingerprintHash() const
+{
+ return d->transportFingerprintHash;
+}
+
+/// Sets the fingerprint hash algorithm for the transport key.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+void QXmppJingleIq::Content::setTransportFingerprintHash(const QString &hash)
+{
+ d->transportFingerprintHash = hash;
+}
+
+/// Returns the setup role for the encrypted transport.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+QString QXmppJingleIq::Content::transportFingerprintSetup() const
+{
+ return d->transportFingerprintSetup;
+}
+
+/// Sets the setup role for the encrypted transport.
+///
+/// This is used for DTLS-SRTP as defined in XEP-0320.
+
+void QXmppJingleIq::Content::setTransportFingerprintSetup(const QString &setup)
+{
+ d->transportFingerprintSetup = setup;
+}
+
/// \cond
void QXmppJingleIq::Content::parse(const QDomElement &element)
{
@@ -290,13 +369,20 @@ void QXmppJingleIq::Content::parse(const QDomElement &element)
d->transportUser = transportElement.attribute("ufrag");
d->transportPassword = transportElement.attribute("pwd");
child = transportElement.firstChildElement("candidate");
- while (!child.isNull())
- {
+ while (!child.isNull()) {
QXmppJingleCandidate candidate;
candidate.parse(child);
d->transportCandidates << candidate;
child = child.nextSiblingElement("candidate");
}
+ child = transportElement.firstChildElement("fingerprint");
+
+ /// XEP-0320
+ if (!child.isNull()) {
+ d->transportFingerprint = parseFingerprint(child.text());
+ d->transportFingerprintHash = child.attribute("hash");
+ d->transportFingerprintSetup = child.attribute("setup");
+ }
}
void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const
@@ -332,6 +418,16 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const
helperToXmlAddAttribute(writer, "pwd", d->transportPassword);
foreach (const QXmppJingleCandidate &candidate, d->transportCandidates)
candidate.toXml(writer);
+
+ // XEP-0320
+ if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) {
+ writer->writeStartElement("fingerprint");
+ writer->writeAttribute("xmlns", ns_jingle_dtls);
+ writer->writeAttribute("hash", d->transportFingerprintHash);
+ writer->writeAttribute("setup", d->transportFingerprintSetup);
+ writer->writeCharacters(formatFingerprint(d->transportFingerprint));
+ writer->writeEndElement();
+ }
writer->writeEndElement();
}
writer->writeEndElement();
@@ -353,6 +449,12 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp)
return false;
}
addTransportCandidate(candidate);
+ } else if (attrName == "fingerprint") {
+ const QStringList bits = attrValue.split(' ');
+ if (bits.size() > 1) {
+ d->transportFingerprintHash = bits[0];
+ d->transportFingerprint = parseFingerprint(bits[1]);
+ }
} else if (attrName == "fmtp") {
int spIdx = attrValue.indexOf(' ');
if (spIdx == -1) {
@@ -400,6 +502,8 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp)
d->transportUser = attrValue;
} else if (attrName == "ice-pwd") {
d->transportPassword = attrValue;
+ } else if (attrName == "setup") {
+ d->transportFingerprintSetup = attrValue;
} else if (attrName == "ssrc") {
const QStringList bits = attrValue.split(' ');
if (bits.isEmpty()) {
@@ -483,6 +587,12 @@ QString QXmppJingleIq::Content::toSdp() const
sdp << QString("a=ice-ufrag:%1").arg(d->transportUser);
if (!d->transportPassword.isEmpty())
sdp << QString("a=ice-pwd:%1").arg(d->transportPassword);
+ if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty())
+ sdp << QString("a=fingerprint:%1 %2").arg(
+ d->transportFingerprintHash,
+ formatFingerprint(d->transportFingerprint));
+ if (!d->transportFingerprintSetup.isEmpty())
+ sdp << QString("a=setup:%1").arg(d->transportFingerprintSetup);
return sdp.join("\r\n") + "\r\n";
}
diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h
index 6ec8f7ec..046b7c96 100644
--- a/src/base/QXmppJingleIq.h
+++ b/src/base/QXmppJingleIq.h
@@ -219,6 +219,16 @@ public:
QString transportPassword() const;
void setTransportPassword(const QString &password);
+ // XEP-0320: Use of DTLS-SRTP in Jingle Sessions
+ QByteArray transportFingerprint() const;
+ void setTransportFingerprint(const QByteArray &fingerprint);
+
+ QString transportFingerprintHash() const;
+ void setTransportFingerprintHash(const QString &hash);
+
+ QString transportFingerprintSetup() const;
+ void setTransportFingerprintSetup(const QString &setup);
+
/// \cond
void parse(const QDomElement &element);
void toXml(QXmlStreamWriter *writer) const;
diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
index 69274739..b7c197be 100644
--- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
+++ b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
@@ -31,9 +31,11 @@ class tst_QXmppJingleIq : public QObject
private slots:
void testCandidate();
- void testRtpSession();
- void testRtpSessionSdp();
- void testRtpSessionSdpParameters();
+ void testContent();
+ void testContentFingerprint();
+ void testContentSdp();
+ void testContentSdpFingerprint();
+ void testContentSdpParameters();
void testSession();
void testTerminate();
void testAudioPayloadType();
@@ -69,18 +71,9 @@ void tst_QXmppJingleIq::testCandidate()
serializePacket(candidate, xml);
};
-void tst_QXmppJingleIq::testRtpSession()
+void tst_QXmppJingleIq::testContent()
{
const QByteArray xml(
-"<iq"
- " id=\"ih28sx61\""
- " to=\"juliet@capulet.lit/balcony\""
- " from=\"romeo@montague.lit/orchard\""
- " type=\"set\">"
- "<jingle xmlns=\"urn:xmpp:jingle:1\""
- " action=\"session-initiate\""
- " initiator=\"romeo@montague.lit/orchard\""
- " sid=\"a73sjjvkla37jfea\">"
"<content creator=\"initiator\" name=\"voice\">"
"<description xmlns=\"urn:xmpp:jingle:apps:rtp:1\" media=\"audio\">"
"<payload-type id=\"96\" name=\"speex\" clockrate=\"16000\"/>"
@@ -114,17 +107,11 @@ void tst_QXmppJingleIq::testRtpSession()
" protocol=\"udp\""
" type=\"srflx\"/>"
"</transport>"
- "</content>"
- "</jingle>"
-"</iq>");
+ "</content>");
- QXmppJingleIq session;
- parsePacket(session, xml);
- QCOMPARE(session.action(), QXmppJingleIq::SessionInitiate);
- QCOMPARE(session.initiator(), QLatin1String("romeo@montague.lit/orchard"));
- QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea"));
+ QXmppJingleIq::Content content;
+ parsePacket(content, xml);
- const QXmppJingleIq::Content &content = session.content();
QCOMPARE(content.creator(), QLatin1String("initiator"));
QCOMPARE(content.name(), QLatin1String("voice"));
QCOMPARE(content.descriptionMedia(), QLatin1String("audio"));
@@ -154,12 +141,63 @@ void tst_QXmppJingleIq::testRtpSession()
QCOMPARE(content.transportUser(), QLatin1String("8hhy"));
QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg"));
- QCOMPARE(session.reason().text(), QString());
- QCOMPARE(session.reason().type(), QXmppJingleIq::Reason::None);
- serializePacket(session, xml);
+ serializePacket(content, xml);
+}
+
+void tst_QXmppJingleIq::testContentFingerprint()
+{
+ const QByteArray xml(
+ "<content creator=\"initiator\" name=\"voice\">"
+ "<description xmlns=\"urn:xmpp:jingle:apps:rtp:1\" media=\"audio\">"
+ "<payload-type id=\"0\" name=\"PCMU\"/>"
+ "</description>"
+ "<transport xmlns=\"urn:xmpp:jingle:transports:ice-udp:1\""
+ " ufrag=\"8hhy\""
+ " pwd=\"asd88fgpdd777uzjYhagZg\">"
+ "<candidate component=\"1\""
+ " foundation=\"1\""
+ " generation=\"0\""
+ " id=\"el0747fg11\""
+ " ip=\"10.0.1.1\""
+ " network=\"1\""
+ " port=\"8998\""
+ " priority=\"2130706431\""
+ " protocol=\"udp\""
+ " type=\"host\"/>"
+ "<fingerprint xmlns=\"urn:xmpp:jingle:apps:dtls:0\" hash=\"sha-256\" setup=\"actpass\">"
+ "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2"
+ "</fingerprint>"
+ "</transport>"
+ "</content>");
+
+ QXmppJingleIq::Content content;
+ parsePacket(content, xml);
+
+ QCOMPARE(content.creator(), QLatin1String("initiator"));
+ QCOMPARE(content.name(), QLatin1String("voice"));
+ QCOMPARE(content.descriptionMedia(), QLatin1String("audio"));
+ QCOMPARE(content.descriptionSsrc(), quint32(0));
+ QCOMPARE(content.payloadTypes().size(), 1);
+ QCOMPARE(content.payloadTypes()[0].id(), quint8(0));
+ QCOMPARE(content.transportCandidates().size(), 1);
+ QCOMPARE(content.transportCandidates()[0].component(), 1);
+ QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1"));
+ QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1"));
+ QCOMPARE(content.transportCandidates()[0].port(), quint16(8998));
+ QCOMPARE(content.transportCandidates()[0].priority(), 2130706431);
+ QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp"));
+ QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType);
+ QCOMPARE(content.transportUser(), QLatin1String("8hhy"));
+ QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg"));
+ QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2"));
+ QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256"));
+ QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass"));
+
+ serializePacket(content, xml);
}
-void tst_QXmppJingleIq::testRtpSessionSdp()
+
+void tst_QXmppJingleIq::testContentSdp()
{
const QString sdp(
"m=audio 8998 RTP/AVP 96 97 18 0 103 98\r\n"
@@ -208,7 +246,47 @@ void tst_QXmppJingleIq::testRtpSessionSdp()
QCOMPARE(content.toSdp(), sdp);
}
-void tst_QXmppJingleIq::testRtpSessionSdpParameters()
+void tst_QXmppJingleIq::testContentSdpFingerprint()
+{
+ const QString sdp(
+ "m=audio 8998 RTP/AVP 96 100\r\n"
+ "c=IN IP4 10.0.1.1\r\n"
+ "a=rtpmap:96 speex/16000\r\n"
+ "a=fmtp:96 cng=on; vbr=on\r\n"
+ "a=rtpmap:100 telephone-event/8000\r\n"
+ "a=fmtp:100 0-15,66,70\r\n"
+ "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n"
+ "a=fingerprint:sha-256 02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2\r\n"
+ "a=setup:actpass\r\n");
+
+ QXmppJingleIq::Content content;
+ QVERIFY(content.parseSdp(sdp));
+
+ QCOMPARE(content.descriptionMedia(), QLatin1String("audio"));
+ QCOMPARE(content.descriptionSsrc(), quint32(0));
+ QCOMPARE(content.payloadTypes().size(), 2);
+ QCOMPARE(content.payloadTypes()[0].id(), quint8(96));
+ QCOMPARE(content.payloadTypes()[0].parameters().value("vbr"), QLatin1String("on"));
+ QCOMPARE(content.payloadTypes()[0].parameters().value("cng"), QLatin1String("on"));
+ QCOMPARE(content.payloadTypes()[1].id(), quint8(100));
+ QCOMPARE(content.payloadTypes()[1].parameters().value("events"), QLatin1String("0-15,66,70"));
+ QCOMPARE(content.transportCandidates().size(), 1);
+ QCOMPARE(content.transportCandidates()[0].component(), 1);
+ QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1"));
+ QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1"));
+ QCOMPARE(content.transportCandidates()[0].port(), quint16(8998));
+ QCOMPARE(content.transportCandidates()[0].priority(), 2130706431);
+ QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp"));
+ QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType);
+ QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2"));
+ QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256"));
+ QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass"));
+
+ QCOMPARE(content.toSdp(), sdp);
+
+}
+
+void tst_QXmppJingleIq::testContentSdpParameters()
{
const QString sdp(
"m=audio 8998 RTP/AVP 96 100\r\n"