Merge pull request #15559 from SimonWalton-HiFi/avatar-theft-challenge

Allow for delayed challenges if salt is empty
This commit is contained in:
Howard Stearns 2019-05-14 16:53:12 -07:00 committed by GitHub
commit ee97e2faa1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 84 deletions

View file

@ -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;

View file

@ -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

View file

@ -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();

View file

@ -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() {

View file

@ -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();