mirror of
https://github.com/lubosz/overte.git
synced 2025-04-07 15:42:13 +02:00
Merge pull request #15559 from SimonWalton-HiFi/avatar-theft-challenge
Allow for delayed challenges if salt is empty
This commit is contained in:
commit
ee97e2faa1
6 changed files with 118 additions and 84 deletions
|
@ -27,6 +27,12 @@
|
|||
#include "ClientTraitsHandler.h"
|
||||
#include "AvatarLogging.h"
|
||||
|
||||
MixerAvatar::~MixerAvatar() {
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void MixerAvatar::fetchAvatarFST() {
|
||||
_verifyState = nonCertified;
|
||||
|
||||
|
@ -229,6 +235,7 @@ void MixerAvatar::processCertifyEvents() {
|
|||
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||
QString ownerPublicKey;
|
||||
bool ownerValid = false;
|
||||
_pendingEvent = false;
|
||||
if (responseJson["status"].toString() == "success") {
|
||||
QJsonValue jsonData = responseJson["data"];
|
||||
if (jsonData.isObject()) {
|
||||
|
@ -251,6 +258,7 @@ void MixerAvatar::processCertifyEvents() {
|
|||
}
|
||||
sendOwnerChallenge();
|
||||
_verifyState = challengeClient;
|
||||
_pendingEvent = true;
|
||||
} else {
|
||||
_verifyState = error;
|
||||
}
|
||||
|
@ -259,7 +267,6 @@ void MixerAvatar::processCertifyEvents() {
|
|||
"message:" << responseJson["message"].toString();
|
||||
_verifyState = error;
|
||||
}
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -295,6 +302,7 @@ void MixerAvatar::processCertifyEvents() {
|
|||
}
|
||||
|
||||
case requestingOwner:
|
||||
case challengeClient:
|
||||
{ // Qt networking done on this thread:
|
||||
QCoreApplication::processEvents();
|
||||
break;
|
||||
|
@ -324,12 +332,21 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
nonceHash.addData(nonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 5 * 1000; // 5 s
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
_challengeTimeout = new QTimer();
|
||||
_challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
_challengeTimeout->setSingleShot(true);
|
||||
_challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
}
|
||||
});
|
||||
_challengeTimeout->start();
|
||||
}
|
||||
|
||||
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
||||
|
@ -337,7 +354,6 @@ void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
|||
QByteArray encryptedNonce;
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
if (_verifyState == challengeClient) {
|
||||
_challengeTimeout.stop();
|
||||
_challengeResponse = response->readAll();
|
||||
_verifyState = challengeResponse;
|
||||
_pendingEvent = true;
|
||||
|
|
|
@ -21,6 +21,7 @@ class ResourceRequest;
|
|||
|
||||
class MixerAvatar : public AvatarData {
|
||||
public:
|
||||
~MixerAvatar();
|
||||
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
|
@ -53,7 +54,7 @@ private:
|
|||
QString _ownerPublicKey;
|
||||
QByteArray _challengeNonceHash;
|
||||
QByteArray _challengeResponse;
|
||||
QTimer _challengeTimeout;
|
||||
QTimer* _challengeTimeout { nullptr };
|
||||
bool _needsIdentityUpdate { false };
|
||||
|
||||
bool generateFSTHash();
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 42 KiB |
|
@ -317,13 +317,13 @@ void Ledger::accountSuccess(QNetworkReply* reply) {
|
|||
const QByteArray locker = data["locker"].toString().toUtf8();
|
||||
bool isOverride = wallet->wasSoftReset();
|
||||
|
||||
wallet->setSalt(salt);
|
||||
wallet->setIv(iv);
|
||||
wallet->setCKey(ckey);
|
||||
if (!locker.isEmpty()) {
|
||||
wallet->setWallet(locker);
|
||||
wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth.
|
||||
}
|
||||
wallet->setSalt(salt);
|
||||
|
||||
QString keyStatus = "ok";
|
||||
QStringList localPublicKeys = wallet->listPublicKeys();
|
||||
|
|
|
@ -313,6 +313,8 @@ Wallet::Wallet() {
|
|||
walletScriptingInterface->setWalletStatus(status);
|
||||
});
|
||||
|
||||
connect(ledger.data(), &Ledger::accountResult, this, &Wallet::sendChallengeOwnershipResponses);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
getWalletStatus();
|
||||
|
@ -823,88 +825,101 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
|
|||
}
|
||||
|
||||
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
_pendingChallenges.push_back(packet);
|
||||
sendChallengeOwnershipResponses();
|
||||
}
|
||||
|
||||
void Wallet::sendChallengeOwnershipResponses() {
|
||||
if (_pendingChallenges.size() == 0 || getSalt().length() == 0) {
|
||||
return;
|
||||
}
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// With EC keys, we receive a nonce from the metaverse server, which is signed
|
||||
// here with the private key and returned. Verification is done at server.
|
||||
|
||||
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||
int status;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
packet->readPrimitive(&idByteArraySize);
|
||||
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
|
||||
if (challengeOriginatedFromClient) {
|
||||
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
// "encryptedText" is now a series of random bytes, a nonce
|
||||
QByteArray id = packet->read(idByteArraySize);
|
||||
QByteArray text = packet->read(textByteArraySize);
|
||||
QByteArray challengingNodeUUID;
|
||||
if (challengeOriginatedFromClient) {
|
||||
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
EC_KEY* ec = readKeys(keyFilePath());
|
||||
QString sig;
|
||||
|
||||
if (ec) {
|
||||
ERR_clear_error();
|
||||
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
|
||||
status = 1;
|
||||
} else {
|
||||
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
|
||||
status = -1;
|
||||
for (const auto& packet: _pendingChallenges) {
|
||||
|
||||
// With EC keys, we receive a nonce from the metaverse server, which is signed
|
||||
// here with the private key and returned. Verification is done at server.
|
||||
|
||||
QString sig;
|
||||
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||
int status;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
packet->readPrimitive(&idByteArraySize);
|
||||
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
|
||||
if (challengeOriginatedFromClient) {
|
||||
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
// "encryptedText" is now a series of random bytes, a nonce
|
||||
QByteArray id = packet->read(idByteArraySize);
|
||||
QByteArray text = packet->read(textByteArraySize);
|
||||
QByteArray challengingNodeUUID;
|
||||
if (challengeOriginatedFromClient) {
|
||||
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
ERR_clear_error();
|
||||
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
|
||||
status = 1;
|
||||
} else {
|
||||
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
|
||||
status = -1;
|
||||
}
|
||||
|
||||
QByteArray textByteArray;
|
||||
if (status > -1) {
|
||||
textByteArray = sig.toUtf8();
|
||||
}
|
||||
textByteArraySize = textByteArray.size();
|
||||
int idSize = id.size();
|
||||
// setup the packet
|
||||
Node& sendingNode = *nodeList->nodeWithLocalID(packet->getSourceID());
|
||||
if (challengeOriginatedFromClient) {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
textPacket->write(challengingNodeUUID);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), sendingNode);
|
||||
} else {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), sendingNode);
|
||||
}
|
||||
|
||||
if (status == -1) {
|
||||
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
|
||||
long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "EC error:" << error_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EC_KEY_free(ec);
|
||||
|
||||
QByteArray textByteArray;
|
||||
if (status > -1) {
|
||||
textByteArray = sig.toUtf8();
|
||||
}
|
||||
textByteArraySize = textByteArray.size();
|
||||
int idSize = id.size();
|
||||
// setup the packet
|
||||
if (challengeOriginatedFromClient) {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
textPacket->write(challengingNodeUUID);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), *sendingNode);
|
||||
} else {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), *sendingNode);
|
||||
}
|
||||
|
||||
if (status == -1) {
|
||||
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
|
||||
long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "EC error:" << error_str;
|
||||
}
|
||||
}
|
||||
_pendingChallenges.clear();
|
||||
}
|
||||
|
||||
void Wallet::account() {
|
||||
|
|
|
@ -94,6 +94,7 @@ signals:
|
|||
|
||||
private slots:
|
||||
void handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void sendChallengeOwnershipResponses();
|
||||
|
||||
private:
|
||||
friend class Ledger;
|
||||
|
@ -104,6 +105,7 @@ private:
|
|||
QByteArray _ckey;
|
||||
QString* _passphrase { nullptr };
|
||||
bool _isOverridingServer { false };
|
||||
std::vector<QSharedPointer<ReceivedMessage>> _pendingChallenges;
|
||||
|
||||
bool writeWallet(const QString& newPassphrase = QString(""));
|
||||
void updateImageProvider();
|
||||
|
|
Loading…
Reference in a new issue