diff --git a/src/core.cpp b/src/core.cpp index 5798e200a..38726c44a 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1107,6 +1107,19 @@ QString Core::getIDString() const // 16^n > 10^10 (which is roughly the planet's population) } +QPair Core::getKeypair() const +{ + QPair keypair; + if (!tox) + return keypair; + + char buf[2*TOX_PUBLIC_KEY_SIZE]; + tox_get_keys(tox, (uint8_t*)buf, (uint8_t*)buf+TOX_PUBLIC_KEY_SIZE); + keypair.first = QByteArray(buf, TOX_PUBLIC_KEY_SIZE); + keypair.second = QByteArray(buf+TOX_PUBLIC_KEY_SIZE, TOX_PUBLIC_KEY_SIZE); + return keypair; +} + QString Core::getStatusMessage() const { QString sname; diff --git a/src/core.h b/src/core.h index f90b7603e..7f87fbfa9 100644 --- a/src/core.h +++ b/src/core.h @@ -72,6 +72,7 @@ public: QString getUsername() const; ///< Returns our username, or an empty string on failure QString getStatusMessage() const; ///< Returns our status message, or an empty string on failure ToxID getSelfId() const; ///< Returns our Tox ID + QPair getKeypair() const; ///< Returns our public and private keys VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source diff --git a/src/main.cpp b/src/main.cpp index 43395a9f4..da83631e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,8 @@ #include +#include "toxme.h" + #ifdef LOG_TO_FILE static QtMessageHandler dflt; static QTextStream* logFile {nullptr}; diff --git a/src/toxme.cpp b/src/toxme.cpp new file mode 100644 index 000000000..e831812ef --- /dev/null +++ b/src/toxme.cpp @@ -0,0 +1,137 @@ +#include "toxme.h" +#include "core.h" +#include +#include +#include +#include +#include +#include + +const QString Toxme::apiUrl{"https://toxme.se/api"}; + +const unsigned char Toxme::pinnedPk[] = {0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14, + 0xEA, 0x4C, 0xF7, 0x27, 0x7A, 0x85, 0x02, 0x7C, 0xD9, 0xF5, 0x19, 0x6D, 0xF1, 0x7E, 0x0B, 0x13}; + +QByteArray Toxme::makeJsonRequest(QString json) +{ + QNetworkAccessManager netman; + QNetworkRequest request{apiUrl}; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QNetworkReply* reply = netman.post(request,json.toUtf8()); + while (!reply->isFinished()) + qApp->processEvents(); + + return reply->readAll(); +} + +QByteArray Toxme::prepareEncryptedJson(int action, QString payload) +{ + QPair keypair = Core::getInstance()->getKeypair(); + if (keypair.first.isEmpty() || keypair.second.isEmpty()) + { + qWarning() << "Toxme::prepareEncryptedJson: Couldn't get our keypair, aborting"; + return QByteArray(); + } + + QByteArray nonce(crypto_box_NONCEBYTES, 0); + randombytes((uint8_t*)nonce.data(), crypto_box_NONCEBYTES); + + QByteArray payloadData = payload.toUtf8(); + const size_t cypherlen = crypto_box_MACBYTES+payloadData.size(); + unsigned char* payloadEnc = new unsigned char[cypherlen]; + + crypto_box_easy(payloadEnc,(uint8_t*)payloadData.data(),payloadData.size(), + (uint8_t*)nonce.data(),pinnedPk,(uint8_t*)keypair.second.data()); + QByteArray payloadEncData(reinterpret_cast(payloadEnc), cypherlen); + delete[] payloadEnc; + + const QString json{"{\"action\":"+QString().setNum(action)+"," + "\"public_key\":\""+keypair.first.toHex()+"\"," + "\"encrypted\":\""+payloadEncData.toBase64()+"\"," + "\"nonce\":\""+nonce.toBase64()+"\"}"}; + return json.toUtf8(); +} + +ToxID Toxme::lookup(QString address) +{ + // JSON injection ? + address.replace('\\',"\\\\"); + address.replace('"',"\""); + + ToxID id; + const QString json{"{\"action\":3,\"name\":\""+address+"\"}"}; + static const QByteArray pattern{"public_key\""}; + + QByteArray response = makeJsonRequest(json); + const int index = response.indexOf(pattern); + if (index == -1) + return id; + response = response.mid(index+pattern.size()); + + const int idStart = response.indexOf('"'); + if (idStart == -1) + return id; + response = response.mid(idStart+1); + + const int idEnd = response.indexOf('"'); + if (idEnd == -1) + return id; + response.truncate(idEnd); + + id = ToxID::fromString(response); + return id; +} + +int Toxme::extractError(QString json) +{ + static const QByteArray pattern{"c\":"}; + + json = json.remove(' '); + const int index = json.indexOf(pattern); + if (index == -1) + return INT_MIN; + json = json.mid(index+pattern.size()); + + const int end = json.indexOf('}'); + if (end == -1) + return INT_MIN; + json.truncate(end); + + bool ok; + int r = json.toInt(&ok); + if (!ok) + return INT_MIN; + return r; +} + +bool Toxme::createAddress(ToxID id, QString address, + bool keepPrivate, QString bio) +{ + int privacy = keepPrivate ? 0 : 2; + // JSON injection ? + bio.replace('\\',"\\\\"); + bio.replace('"',"\""); + address.replace('\\',"\\\\"); + address.replace('"',"\""); + + const QString payload{"{\"tox_id\":\""+id.toString()+"\"," + "\"name\":\""+address+"\"," + "\"privacy\":"+QString().setNum(privacy)+"," + "\"bio\":\""+bio+"\"," + "\"timestamp\":"+QString().setNum(time(0))+"}"}; + + QByteArray response = makeJsonRequest(prepareEncryptedJson(1,payload)); + + return (extractError(response) == 0); +} + +bool Toxme::deleteAddress(ToxID id) +{ + const QString payload{"{\"public_key\":\""+id.toString().left(64)+"\"," + "\"timestamp\":"+QString().setNum(time(0))+"}"}; + + QByteArray response = makeJsonRequest(prepareEncryptedJson(2,payload)); + + return (extractError(response) == 0); +} diff --git a/src/toxme.h b/src/toxme.h new file mode 100644 index 000000000..1382b45c7 --- /dev/null +++ b/src/toxme.h @@ -0,0 +1,38 @@ +#ifndef TOXME_H +#define TOXME_H + +#include +#include +#include +#include "corestructs.h" + +class QNetworkAccessManager; + +/// This class implements a client for the toxme.se API +/// The class is thread safe +/// May process events while waiting for blocking calls +class Toxme +{ +public: + /// Converts a toxme.se address to a Tox ID, returns an empty ID on error + static ToxID lookup(QString address); + /// Creates a new toxme.se address associated with a Tox ID. + /// If keepPrivate, the address will not be published on toxme.se + /// The bio is a short optional description of yourself if you want to publish your address. + static bool createAddress(ToxID id, QString address, + bool keepPrivate=true, QString bio=QString()); + /// Deletes the address associated with your current Tox ID + static bool deleteAddress(ToxID id); + +private: + Toxme()=delete; + static QByteArray makeJsonRequest(QString json); + static QByteArray prepareEncryptedJson(int action, QString payload); + static int extractError(QString json); + +private: + static const QString apiUrl; + static const unsigned char pinnedPk[]; +}; + +#endif // TOXME_H