From 9e570fdba3c76b0eda6ff91758afed890d47e648 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 11 Oct 2017 13:05:46 -0700 Subject: [PATCH] First pass at encryption/decryption. Exciting! --- interface/src/commerce/Wallet.cpp | 21 +++--- interface/src/commerce/Wallet.h | 2 - libraries/entities/src/EntityTree.cpp | 102 +++++++++++++++++++------- libraries/entities/src/EntityTree.h | 5 ++ 4 files changed, 91 insertions(+), 39 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index dd926d00d4..702a94625a 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -718,15 +718,22 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack QString decryptedText; int certIDByteArraySize; int encryptedTextByteArraySize; - int ownerKeyByteArraySize; packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize); - packet->readPrimitive(&ownerKeyByteArraySize); QByteArray certID = packet->read(certIDByteArraySize); + QByteArray encryptedText = packet->read(encryptedTextByteArraySize); - if (verifyOwnerChallenge(packet->read(encryptedTextByteArraySize), packet->read(ownerKeyByteArraySize), decryptedText)) { + const auto text = reinterpret_cast(encryptedText.constData()); + const unsigned int textLength = encryptedText.length(); + + RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); + + const int decryptionStatus = RSA_private_decrypt(textLength, text, reinterpret_cast(encryptedText.data()), rsa, RSA_PKCS1_OAEP_PADDING); + RSA_free(rsa); + + if (decryptionStatus != -1) { auto nodeList = DependencyManager::get(); QByteArray decryptedTextByteArray = decryptedText.toUtf8(); @@ -744,16 +751,10 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); } else { - qCDebug(commerce) << "verifyOwnerChallenge() returned false"; + qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; } } -bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText) { - // I have no idea how to do this yet, so here's some dummy code that may not even work. - decryptedText = QString("success"); - return true; -} - void Wallet::account() { auto ledger = DependencyManager::get(); ledger->account(); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 38c5299810..16d23c1e5b 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -81,8 +81,6 @@ private: bool writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath); bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); - bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText); - void account(); }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index a479c04459..2a8e3e575a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -13,6 +13,10 @@ #include #include +#include +#include +#include + #include #include @@ -1144,13 +1148,49 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) } void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { - qCDebug(entities) << "'transfer_status' is 'pending', checking again in 10 seconds..." << entityItemID; + qCDebug(entities) << "'transfer_status' is 'pending', checking again in 90 seconds..." << entityItemID; QTimer* transferStatusRetryTimer = new QTimer(this); connect(transferStatusRetryTimer, &QTimer::timeout, this, [=]() { validatePop(certID, entityItemID, senderNode, true); }); transferStatusRetryTimer->setSingleShot(true); - transferStatusRetryTimer->start(10000); + transferStatusRetryTimer->start(90000); +} + +QString EntityTree::computeEncryptedNonce(const QString& certID, const QString& ownerKey) { + QUuid nonce = QUuid::createUuid(); + const auto text = reinterpret_cast(qPrintable(nonce.toString())); + const unsigned int textLength = nonce.toString().length(); + + const auto publicKey = reinterpret_cast(ownerKey.toUtf8().toBase64().constData()); + BIO* bio = BIO_new_mem_buf((void*)publicKey, sizeof(publicKey)); + RSA* rsa = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL); + + QByteArray encryptedText(RSA_size(rsa), 0); + const int encryptStatus = RSA_public_encrypt(textLength, text, reinterpret_cast(encryptedText.data()), rsa, RSA_PKCS1_OAEP_PADDING); + BIO_free(bio); + RSA_free(rsa); + if (encryptStatus == -1) { + qCWarning(entities) << "Unable to compute encrypted nonce for" << certID; + return ""; + } + + { + QWriteLocker locker(&_certNonceMapLock); + _certNonceMap.insert(certID, nonce); + } + + return encryptedText.toBase64(); +} + +bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) { + QString actualNonce; + { + QWriteLocker locker(&_certNonceMapLock); + actualNonce = _certNonceMap.take(certID).toString(); + } + + return actualNonce == decryptedNonce; } void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { @@ -1172,6 +1212,9 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + + // ZRF FIXME Remove these two lines QJsonDocument doc(jsonObject); qCDebug(entities) << "ZRF FIXME" << doc.toJson(QJsonDocument::Compact); @@ -1207,32 +1250,37 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt // Second, challenge ownership of the PoP cert // 1. Encrypt a nonce with the owner's public key QString ownerKey(jsonObject["owner_public_key"].toString()); - QString encryptedText("test"); + QString encryptedText = computeEncryptedNonce(certID, ownerKey); - // 2. Send the encrypted text to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - QByteArray encryptedTextByteArray = encryptedText.toUtf8(); - int encryptedTextByteArraySize = encryptedTextByteArray.size(); - QByteArray ownerKeyByteArray = ownerKey.toUtf8(); - int ownerKeyByteArraySize = ownerKeyByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + encryptedTextByteArraySize + ownerKeyByteArraySize + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); - challengeOwnershipPacket->writePrimitive(ownerKeyByteArraySize); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedTextByteArray); - challengeOwnershipPacket->write(ownerKeyByteArray); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); - - // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - return; + if (encryptedText == "") { + qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; + deleteEntity(entityItemID, true); + QWriteLocker locker(&_recentlyDeletedEntitiesLock); + _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); } else { - startChallengeOwnershipTimer(entityItemID); + // 2. Send the encrypted text to the rezzing avatar's node + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + QByteArray encryptedTextByteArray = encryptedText.toUtf8(); + int encryptedTextByteArraySize = encryptedTextByteArray.size(); + QByteArray ownerKeyByteArray = ownerKey.toUtf8(); + int ownerKeyByteArraySize = ownerKeyByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + certIDByteArraySize + encryptedTextByteArraySize + 2 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedTextByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); + + // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + return; + } else { + startChallengeOwnershipTimer(entityItemID); + } } } } else { @@ -1260,7 +1308,7 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const emit killChallengeOwnershipTimeoutTimer(certID); - if (decryptedText == "fail") { + if (!verifyDecryptedNonce(certID, decryptedText)) { EntityItemID id; { QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 4a773f7a15..fc6a913ffe 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -336,6 +336,9 @@ protected: mutable QReadWriteLock _entityCertificateIDMapLock; QHash _entityCertificateIDMap; + mutable QReadWriteLock _certNonceMapLock; + QHash _certNonceMap; + EntitySimulationPointer _simulation; bool _wantEditLogging = false; @@ -382,6 +385,8 @@ protected: Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: + QString computeEncryptedNonce(const QString& certID, const QString& ownerKey); + bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); };