diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2015-08-28 14:17:16 +0200 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2015-08-28 14:17:16 +0200 |
| commit | 721836a76a028982495ccab6758a2d34a149fff3 (patch) | |
| tree | b09f1ca850b3780b617c968f82e18846ef26baf6 | |
| parent | b447fb1a91b0e4e5c46700c123aa8d79ed711891 (diff) | |
| download | qxmpp-721836a76a028982495ccab6758a2d34a149fff3.tar.gz | |
parse DTLS-SRTP attributes according to XEP-0320
| -rw-r--r-- | src/base/QXmppJingleIq.cpp | 114 | ||||
| -rw-r--r-- | src/base/QXmppJingleIq.h | 10 | ||||
| -rw-r--r-- | tests/qxmppjingleiq/tst_qxmppjingleiq.cpp | 132 |
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" |
