From 1460611cce6a17754aa679dfd04b2fdc0d91f4fb Mon Sep 17 00:00:00 2001 From: Prasenjit Kumar Shaw Date: Thu, 18 Jul 2019 23:03:36 +0530 Subject: [PATCH] Add method to get Browser ID Certificate from Firefox Server --- src/lib/sync/hawk/hawk.cpp | 51 ++------- src/lib/sync/hawk/hawk.h | 1 - src/lib/sync/synccrypto.cpp | 1 + src/lib/sync/syncmanager.cpp | 209 +++++++++++++++++++++-------------- src/lib/sync/syncmanager.h | 18 ++- 5 files changed, 145 insertions(+), 135 deletions(-) diff --git a/src/lib/sync/hawk/hawk.cpp b/src/lib/sync/hawk/hawk.cpp index 6dbe14cc9..1efa6d274 100644 --- a/src/lib/sync/hawk/hawk.cpp +++ b/src/lib/sync/hawk/hawk.cpp @@ -40,17 +40,6 @@ HawkOptions::HawkOptions(const char* app, const char* dlg, const char* ext, cons m_nonce = new QByteArray(nonce); m_payload = new QByteArray(payload); m_timestamp = new QByteArray(timestamp); - - qDebug() << "New HawkOptions created: " - << "\n m_app: " << m_app->data() - << "\n m_dlg: " << m_dlg->data() - << "\n m_ext: " << m_ext->data() - << "\n m_contentType: " << m_contentType->data() - << "\n m_hash: " << m_hash->data() - << "\n m_localTimeOffset: " << m_localTimeOffset->data() - << "\n m_nonce: " << m_nonce->data() - << "\n m_payload: " << m_payload->data() - << "\n m_timestamp: " << m_timestamp->data(); } HawkOptions::~HawkOptions() @@ -88,18 +77,6 @@ HawkArtifacts::HawkArtifacts(const char* app, const char* dlg, const char* ext, } else { m_timestamp = new QByteArray(); } - - qDebug() << "New HawkOptions created: " - << "\n m_app: " << m_app->data() - << "\n m_dlg: " << m_dlg->data() - << "\n m_ext: " << m_ext->data() - << "\n m_hash: " << m_hash->data() - << "\n m_host: " << m_host->data() - << "\n m_method: " << m_method->data() - << "\n m_nonce: " << m_nonce->data() - << "\n m_resource: " << m_resource->data() - << "\n m_port: " << m_port->data() - << "\n m_timestamp: " << m_timestamp->data(); } HawkArtifacts::~HawkArtifacts() @@ -117,17 +94,13 @@ HawkArtifacts::~HawkArtifacts() HawkHeader::HawkHeader(const char* url, const char* method, const char* id, const char* key, size_t keyLen, HawkOptions* option) { - qDebug() << "Inside HawkHeader constructor"; qint64 ts = QDateTime::currentSecsSinceEpoch(); - qDebug() << "timestamp: " << ts; QString *hash = new QString(option->m_hash->data()); QString *payload = new QString(option->m_payload->data()); QString *timestamp = new QString(option->m_timestamp->data()); - qDebug() << "Debug#1"; QUrl uri = QUrl(QString(url)); - qDebug() << "URL query: " << uri.query() << " length: " << uri.query().length(); QString *resource = (uri.query().length() == 0) ? (new QString(uri.path())) : (new QString(uri.path() + QString("?") + uri.query())); @@ -136,7 +109,6 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons if (option->m_nonce->length() > 0) { nonce = new QString(option->m_nonce->data()); } else { - qDebug() << "Creating random nonce"; u_char *bytes = new u_char(NONCE_LEN / 2); generateRandomBytes(nullptr, NONCE_LEN / 2, bytes); QByteArray temp = QByteArray::fromRawData((const char *)bytes, NONCE_LEN / 2); @@ -148,7 +120,6 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons QString *localTimeOffset = new QString(option->m_localTimeOffset->data()); quint64 offset = 0; if (localTimeOffset->length() > 0) { - qDebug() << "Adding offset using localTimeOffset"; offset = localTimeOffset->toInt(nullptr, 10); } ts = timestamp->toInt(nullptr, 10) + offset; @@ -165,7 +136,6 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons QByteArray *tempPayload = new QByteArray(); tempPayload->append(option->m_payload->data()); - qDebug() << "Going to hawkComputePayloadHash"; QByteArray *tempHash = hawkComputePayloadHash(tempPayload, contentType); hash = new QString(tempHash->data()); @@ -182,7 +152,6 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons } qint64 port = uri.port(defaultPort); - qDebug() << "Creating Hawk Artifact"; m_artifacts = new HawkArtifacts(option ? option->m_app->data() : nullptr, option ? option->m_dlg->data() : nullptr, option ? option->m_ext->data() : nullptr, @@ -198,7 +167,7 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons if (m_artifacts->m_hash->length() > 0) { QByteArray *name = new QByteArray("hash"); - QByteArray *value = new QByteArray(m_artifacts->m_hash->toHex().data()); + QByteArray *value = new QByteArray(m_artifacts->m_hash->data()); header = hawkAppendToHeader(header, name, value); delete name; delete value; @@ -252,8 +221,6 @@ HawkHeader::HawkHeader(const char* url, const char* method, const char* id, cons delete resource; delete nonce; delete header; - - qDebug() << "Inside newHawkHeader, header data: " << m_header->data(); } HawkHeader::~HawkHeader() @@ -265,12 +232,13 @@ HawkHeader::~HawkHeader() QByteArray * HawkHeader::hawkParseContentType(QByteArray* contentType) { QByteArray temp; - int index = contentType->indexOf(';', 0); - temp.append(*contentType); - temp.truncate(index + 1); + int index = contentType->indexOf(';'); + temp.append(contentType->data()); + if (index > 0) { + temp.truncate(index); + } QByteArray *ret = new QByteArray(); ret->append(temp.toLower()); - qDebug() << "Inside hawkParseContentType: " << ret->data(); return ret; } @@ -295,8 +263,6 @@ QByteArray * HawkHeader::hawkComputePayloadHash(QByteArray* payload, QByteArray* delete content; delete outTemp; - qDebug() << "Payload: " << payload->data(); - qDebug() << "Inside hawkComputePayloadHash: " << out->data(); return out; } @@ -304,7 +270,6 @@ QByteArray * HawkHeader::hawkAppendToHeader(QByteArray* header, QByteArray* name { QString tempString = QString("%1, %2=\"%3\"").arg(header->data()).arg(name->data()).arg(value->data()); QByteArray *out = new QByteArray(tempString.toUtf8().data()); - qDebug() << "Inside hawkAppendToHeader: " << out->data(); return out; } @@ -344,7 +309,6 @@ QByteArray * HawkHeader::hawkNormalizeString(QByteArray* type, HawkArtifacts* ar } QByteArray *out = new QByteArray(normalized.toUtf8().data()); - qDebug() << "Inside hawkNormalizeString:\n" << out->data(); return out; } @@ -358,5 +322,6 @@ QByteArray * HawkHeader::hawkComputeMac(QByteArray* type, QByteArray* key, size_ u_char *out = new u_char(SHA256_DIGEST_SIZE); hmac_sha256_digest(ctx, SHA256_DIGEST_SIZE, out); QByteArray *mac = new QByteArray(QByteArray::fromRawData((const char *)out, SHA256_DIGEST_SIZE)); - return mac; // convert to hex and then to base64 encoding before using + + return mac; } diff --git a/src/lib/sync/hawk/hawk.h b/src/lib/sync/hawk/hawk.h index 1631848b0..526b100e6 100644 --- a/src/lib/sync/hawk/hawk.h +++ b/src/lib/sync/hawk/hawk.h @@ -75,4 +75,3 @@ private: QByteArray *hawkComputeMac(QByteArray *type, QByteArray *key, size_t keyLen, HawkArtifacts *artifact); }; - diff --git a/src/lib/sync/synccrypto.cpp b/src/lib/sync/synccrypto.cpp index 4a1169ebd..61cfdbade 100644 --- a/src/lib/sync/synccrypto.cpp +++ b/src/lib/sync/synccrypto.cpp @@ -25,6 +25,7 @@ #include #include +#include #include diff --git a/src/lib/sync/syncmanager.cpp b/src/lib/sync/syncmanager.cpp index 80743fba4..7d93ab617 100644 --- a/src/lib/sync/syncmanager.cpp +++ b/src/lib/sync/syncmanager.cpp @@ -38,7 +38,6 @@ SyncCredentials::SyncCredentials() SyncCredentials::~SyncCredentials() { - qDebug() << "Deleting SyncCreds..."; } void SyncCredentials::addSyncCredentials(QString key, QString value) @@ -75,16 +74,13 @@ SyncState::SyncState() SyncState::~SyncState() { delete m_lastSyncTime; - qDebug() << "Deleting SyncState..."; } void SyncState::saveSyncState(bool syncSuccess) { - qDebug() << "Entered saveSyncState syncSuccess=" << syncSuccess; if (syncSuccess) { *m_lastSyncTime = QDateTime::currentDateTimeUtc(); m_isInitialSync = false; - qDebug() << "lastSyncTime = " << m_lastSyncTime->toString() << " isInitialSync= " << m_isInitialSync; Settings settings; settings.beginGroup(QSL("SyncState")); settings.setValue(QSL("IsInitialSync"), m_isInitialSync); @@ -110,20 +106,19 @@ SyncManager::SyncManager(QObject *parent) m_syncCreds = new SyncCredentials(); m_syncState = new SyncState(); m_networkManager = new QNetworkAccessManager(this); + m_keyPair = generateRSAKeyPair(); } SyncManager::~SyncManager() { - qDebug() << "Deleting SyncManager..."; saveSyncState(); delete m_syncCreds; delete m_syncState; - //m_networkManager->deleteLater(); + delete m_keyPair; } void SyncManager::saveSyncState() { - qDebug() << QSL("saveSyncState(%1)").arg(m_syncSuccess ? "true" : "false"); m_syncState->saveSyncState(m_syncSuccess); } @@ -135,14 +130,11 @@ void SyncManager::sync() if (!error) { m_storageCredentialsExpired = false; } - qDebug() << "Got Browser Certs..."; + } else { + qDebug() << "Getting Crypto Keys"; + getCryptoKeys(); } - /* - if(true) { - qDebug() << "Getting Crypto Keys"; - getCryptoKeys(); - } - */ + if (error) { m_syncSuccess = false; qDebug() << "Sync Failed"; @@ -163,8 +155,7 @@ bool SyncManager::getBrowserSignedCertificate() QString endpoint("certificate/sign"); - RSAKeyPair *keyPair = generateRSAKeyPair(); - QByteArray *sessionToken = new QByteArray(m_syncCreds->getValue("SessionToken").toUtf8().data()); + QByteArray *sessionToken = new QByteArray(QByteArray::fromHex(m_syncCreds->getValue("SessionToken").toUtf8().data())); QByteArray *tokenId = new QByteArray(); QByteArray *requestHMACKey = new QByteArray(); QByteArray *requestKey = new QByteArray(); @@ -172,12 +163,10 @@ bool SyncManager::getBrowserSignedCertificate() QByteArray *tokenIdHex = new QByteArray(tokenId->toHex().data()); - qDebug() << "TokenId hex: " << tokenIdHex->data(); - char *n; char *e; - n = mpz_get_str(nullptr, 10, keyPair->m_publicKey.n); - e = mpz_get_str(nullptr, 10, keyPair->m_publicKey.e); + n = mpz_get_str(nullptr, 10, m_keyPair->m_publicKey.n); + e = mpz_get_str(nullptr, 10, m_keyPair->m_publicKey.e); QJsonObject objBody; QJsonObject objKey; @@ -188,11 +177,15 @@ bool SyncManager::getBrowserSignedCertificate() objBody.insert("publicKey", objKey); QJsonDocument doc(objBody); - QByteArray *requestBody = new QByteArray(doc.toJson(QJsonDocument::Compact)); + QByteArray *data = new QByteArray(doc.toJson(QJsonDocument::Compact)); - createHawkPostReqeuest(endpoint, tokenIdHex, requestHMACKey, 32, requestBody); + QNetworkRequest request = createHawkPostReqeuest(endpoint, tokenIdHex, requestHMACKey, 32, data); + + QNetworkReply *reply = m_networkManager->post(request, *data); + connect(reply, &QNetworkReply::finished, this, &SyncManager::recievedBrowserSignedCertificate); + + qDebug() << "Hawk POST Request sent to /certificate/sign endpoint."; - delete keyPair; delete sessionToken; delete tokenId; delete requestHMACKey; @@ -200,15 +193,86 @@ bool SyncManager::getBrowserSignedCertificate() delete tokenIdHex; delete n; delete e; - delete requestBody; + delete data; return error; } +void SyncManager::recievedBrowserSignedCertificate() +{ + QNetworkReply* reply = qobject_cast(sender()); + if (!reply) { + return; + } + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Error in recieving Browser Signed Certificate for Firefox Sync."; + reply->close(); + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(response); + QJsonObject obj = doc.object(); + + if (obj.contains(QString("cert"))) { + QByteArray certificate(obj.value(QString("cert")).toString().toUtf8()); + if (verifyBrowserSignedCertificate(&certificate)) { + qDebug() << "Valid Browser Signed Certificate Response."; + m_storageCredentialsExpired = false; + sync(); + } else { + qWarning() << "Invalid Browser ID Assertion Response Recieved."; + } + } else { + qWarning() << "Invalid Browser ID Assertion Response Recieved."; + } + + reply->deleteLater(); +} + +bool SyncManager::verifyBrowserSignedCertificate(QByteArray* certificate) +{ + QList list = certificate->split('.'); + + QByteArray header(QByteArray::fromBase64(list[0])); + QByteArray payload(QByteArray::fromBase64(list[1])); + qDebug() << "header: " << header << "\n" << "payload: " << payload; + + if (!header.size() || !payload.size()) { + qDebug() << "header or payoad empty"; + return false; + } + + QJsonDocument headerDoc = QJsonDocument::fromJson(header); + QJsonObject headerJson = headerDoc.object(); + QJsonDocument payloadDoc = QJsonDocument::fromJson(payload); + QJsonObject payloadJson = payloadDoc.object(); + + if (!headerJson.contains("alg") || !payloadJson.contains("principal")) { + qDebug() << "invalid header or payload"; + return false; + } + if (headerJson.value("alg").toString() != QString("RS256")) { + return false; + } + + QJsonObject princial = payloadJson.value("principal").toObject(); + QString email = princial.value("email").toString(); + QString uid = m_syncCreds->getValue("UID"); + QString expected = QSL("%1@%2").arg(uid).arg(QUrl(m_FxAServerUrl).host()); + if (expected != email) { + return false; + } + return true; +} + + bool SyncManager::getCryptoKeys() { QString endpoint("account/keys"); - QByteArray *keyFetchToken = new QByteArray(m_syncCreds->getValue("KeyFetchToken").toUtf8().data()); + QByteArray *keyFetchToken = new QByteArray(QByteArray::fromHex(m_syncCreds->getValue("KeyFetchToken").toUtf8().data())); QByteArray *tokenId = new QByteArray(); QByteArray *reqHMACKey = new QByteArray(); QByteArray *respHMACKey = new QByteArray(); @@ -217,37 +281,7 @@ bool SyncManager::getCryptoKeys() QByteArray *tokenIdHex = new QByteArray(tokenId->toHex().data()); - createHawkGetRequest(endpoint, tokenIdHex, reqHMACKey, 32); - - delete keyFetchToken; - delete tokenId; - delete reqHMACKey; - delete respHMACKey; - delete respXorKey; - delete tokenIdHex; - - return false; -} - - -void SyncManager::createHawkGetRequest(QString endpoint, QByteArray* id, QByteArray* key, size_t keyLen) -{ - QString url = m_FxAServerUrl + QString("/") + endpoint; - - QNetworkRequest request; - request.setUrl(QUrl(url.toUtf8())); - qDebug() << "Hawk GET req"; - - HawkOptions *nullOption = new HawkOptions(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); - HawkHeader *header = new HawkHeader(url.toUtf8(), "GET", id->data(), key->data(), keyLen, nullOption); - - request.setRawHeader("Authorization", header->m_header->data()); - - QList headerList = request.rawHeaderList(); - for (int i = 0; i < headerList.size(); ++i) { - QByteArray item = headerList[i]; - qDebug() << " " << item.data() << " : " << request.rawHeader(item).data(); - } + QNetworkRequest request = createHawkGetRequest(endpoint, tokenIdHex, reqHMACKey, 32); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [ = ]() { @@ -261,11 +295,36 @@ void SyncManager::createHawkGetRequest(QString endpoint, QByteArray* id, QByteAr reply->deleteLater(); }); - delete nullOption; - delete header; + delete keyFetchToken; + delete tokenId; + delete reqHMACKey; + delete respHMACKey; + delete respXorKey; + delete tokenIdHex; + + return false; } -void SyncManager::createHawkPostReqeuest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen, QByteArray *data) + +QNetworkRequest SyncManager::createHawkGetRequest(QString endpoint, QByteArray* id, QByteArray* key, size_t keyLen) +{ + QString url = m_FxAServerUrl + QString("/") + endpoint; + + QNetworkRequest request; + request.setUrl(QUrl(url.toUtf8())); + + HawkOptions *nullOption = new HawkOptions(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + HawkHeader *header = new HawkHeader(url.toUtf8(), "GET", id->data(), key->data(), keyLen, nullOption); + + request.setRawHeader(QByteArray("Authorization"), *(header->m_header)); + + delete nullOption; + delete header; + + return request; +} + +QNetworkRequest SyncManager::createHawkPostReqeuest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen, QByteArray *data) { QString contentType = QString("application/json; charset=utf-8"); @@ -274,37 +333,15 @@ void SyncManager::createHawkPostReqeuest(QString endpoint, QByteArray *id, QByte QNetworkRequest request; request.setUrl(QUrl(url.toUtf8())); - qDebug() << "Creating Hawk Option"; HawkOptions *options = new HawkOptions(nullptr, nullptr, nullptr, contentType.toUtf8(), nullptr, nullptr, nullptr, data->data(), nullptr); - qDebug() << "Creating Hawk Header"; HawkHeader *header = new HawkHeader(url.toUtf8(), "POST", id->data(), key->data(), keyLen, options); - qDebug() << "Inside createHawkPostReqeuest header data: " << header->m_header->data(); - request.setRawHeader("Authorization", header->m_header->data()); - request.setRawHeader("Content-Type", contentType.toUtf8()); + request.setRawHeader(QByteArray("Authorization"), *(header->m_header)); + request.setRawHeader(QByteArray("Content-Type"), QByteArray(contentType.toUtf8())); QByteArray contentLength(QSL("%1").arg(data->size()).toUtf8()); - request.setRawHeader("Content-Length", contentLength); - - qDebug() << "Request content:"; - QList headerList = request.rawHeaderList(); - for (int i = 0; i < headerList.size(); ++i) { - QByteArray item = headerList[i]; - qDebug() << " " << item.data() << " : " << request.rawHeader(item).data(); - } - qDebug() << " Data: " << data->data(); - qDebug() << " Url: " << url; - - QNetworkReply *reply = m_networkManager->post(request, *data); - - connect(reply, &QNetworkReply::finished, this, [ = ]() { - reply->deleteLater(); - if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Reply Error" << reply->error() << reply->errorString(); - qDebug() << "Reply: " << reply->readAll(); - return; - } - qDebug() << "Reply: " << reply->readAll(); - }); + request.setRawHeader(QByteArray("Content-Length"), QByteArray(contentLength)); delete options; delete header; + + return request; } diff --git a/src/lib/sync/syncmanager.h b/src/lib/sync/syncmanager.h index 4f4dda05c..a08addaa1 100644 --- a/src/lib/sync/syncmanager.h +++ b/src/lib/sync/syncmanager.h @@ -17,6 +17,7 @@ * ============================================================ */ #pragma once #include "hawk.h" +#include "synccrypto.h" #include #include @@ -31,7 +32,7 @@ class QByteArray; class SyncCredentials { public: - SyncCredentials(); // load the sync credentials from settings + SyncCredentials(); ~SyncCredentials(); void addSyncCredentials(QString key, QString value); QString getValue(QString key); @@ -54,22 +55,28 @@ private: class SyncManager : public QObject { + Q_OBJECT + public: - explicit SyncManager(QObject *parent = nullptr); // load m_syncCreds + explicit SyncManager(QObject *parent = nullptr); ~SyncManager(); - void sync(); // call it from fxalogin.cpp/mainapplication.cpp after recieving sessionToken + void sync(); void saveSyncState(); public Q_SLOTS: void startSync(); +private Q_SLOTS: + void recievedBrowserSignedCertificate(); + private: bool getBrowserSignedCertificate(); bool getCryptoKeys(); - void createHawkPostReqeuest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen, QByteArray *data); - void createHawkGetRequest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen); + bool verifyBrowserSignedCertificate(QByteArray *certificate); + QNetworkRequest createHawkPostReqeuest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen, QByteArray *data); + QNetworkRequest createHawkGetRequest(QString endpoint, QByteArray *id, QByteArray *key, size_t keyLen); bool m_storageCredentialsExpired = true; bool m_syncSuccess = false; @@ -80,4 +87,5 @@ private: QNetworkAccessManager *m_networkManager; SyncCredentials *m_syncCreds; SyncState *m_syncState; + RSAKeyPair *m_keyPair; };