mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 04:53:25 +02:00
Avatar-owner challenge now working
This commit is contained in:
parent
1e7669dd0c
commit
a3cd5ad3c5
4 changed files with 95 additions and 7 deletions
|
@ -82,6 +82,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||||
this, "handleOctreePacket");
|
this, "handleOctreePacket");
|
||||||
|
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership");
|
||||||
|
|
||||||
packetReceiver.registerListenerForTypes({
|
packetReceiver.registerListenerForTypes({
|
||||||
PacketType::ReplicatedAvatarIdentity,
|
PacketType::ReplicatedAvatarIdentity,
|
||||||
|
@ -1123,6 +1124,16 @@ void AvatarMixer::entityChange() {
|
||||||
_dirtyHeroStatus = true;
|
_dirtyHeroStatus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMixer::handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||||
|
if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) {
|
||||||
|
auto clientData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||||
|
auto avatar = clientData->getAvatarSharedPointer();
|
||||||
|
if (avatar) {
|
||||||
|
avatar->handleChallengeResponse(message.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarMixer::aboutToFinish() {
|
void AvatarMixer::aboutToFinish() {
|
||||||
DependencyManager::destroy<ResourceManager>();
|
DependencyManager::destroy<ResourceManager>();
|
||||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||||
|
|
|
@ -65,6 +65,7 @@ private slots:
|
||||||
void domainSettingsRequestComplete();
|
void domainSettingsRequestComplete();
|
||||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
|
void handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -24,6 +24,15 @@
|
||||||
#include "MixerAvatar.h"
|
#include "MixerAvatar.h"
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/aes.h>
|
||||||
|
#include <openssl/ecdsa.h>
|
||||||
|
|
||||||
|
|
||||||
void MixerAvatar::fetchAvatarFST() {
|
void MixerAvatar::fetchAvatarFST() {
|
||||||
_verifyState = kNoncertified;
|
_verifyState = kNoncertified;
|
||||||
_certificateIdFromURL.clear();
|
_certificateIdFromURL.clear();
|
||||||
|
@ -137,17 +146,17 @@ bool MixerAvatar::generateFSTHash() {
|
||||||
|
|
||||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
||||||
// Guess we should refactor this stuff into a Authorization namespace ...
|
// Guess we should refactor this stuff into a Authorization namespace ...
|
||||||
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
||||||
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
||||||
QStringList fstLines = fstFile.split("\n", QString::SkipEmptyParts);
|
QStringList fstLines = fstFile.split("\n", QString::SkipEmptyParts);
|
||||||
static const QString fstKeywordsReg{
|
static const QString fstKeywordsReg {
|
||||||
"(marketplaceID|itemDescription|itemCategories|itemArtist|itemLicenseUrl|limitedRun|itemName|"
|
"(marketplaceID|itemDescription|itemCategories|itemArtist|itemLicenseUrl|limitedRun|itemName|"
|
||||||
"filename|texdir|script|editionNumber|certificateID)"
|
"filename|texdir|script|editionNumber|certificateID)"
|
||||||
};
|
};
|
||||||
QRegularExpression fstLineRegExp{ QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" };
|
QRegularExpression fstLineRegExp { QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" };
|
||||||
QStringListIterator fstLineIter(fstLines);
|
QStringListIterator fstLineIter(fstLines);
|
||||||
|
|
||||||
QJsonObject certifiedItems;
|
QJsonObject certifiedItems;
|
||||||
|
@ -193,7 +202,7 @@ QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
||||||
|
|
||||||
void MixerAvatar::processCertifyEvents() {
|
void MixerAvatar::processCertifyEvents() {
|
||||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||||
if (_verifyState != kOwnerResponse) {
|
if (_verifyState != kOwnerResponse && _verifyState != kChallengeResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +227,10 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ownerValid && !ownerPublicKey.isEmpty()) {
|
if (ownerValid && !ownerPublicKey.isEmpty()) {
|
||||||
// Challenge owner ...
|
_ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n"
|
||||||
|
+ ownerPublicKey
|
||||||
|
+ "\n-----END PUBLIC KEY-----\n";
|
||||||
|
challengeOwner();
|
||||||
} else {
|
} else {
|
||||||
_verifyState = kError;
|
_verifyState = kError;
|
||||||
}
|
}
|
||||||
|
@ -230,5 +242,63 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case kChallengeResponse:
|
||||||
|
{
|
||||||
|
int avatarIDLength;
|
||||||
|
int signedNonceLength;
|
||||||
|
if (_challengeResponse.length() < 8) {
|
||||||
|
_verifyState = kError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream responseStream(_challengeResponse);
|
||||||
|
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
responseStream >> avatarIDLength >> signedNonceLength;
|
||||||
|
QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength);
|
||||||
|
QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||||
|
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||||
|
nonceHash.addData(_challengeNonce);
|
||||||
|
|
||||||
|
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, nonceHash.result(),
|
||||||
|
QByteArray::fromBase64(signedNonce));
|
||||||
|
_verifyState = challengeResult ? kVerificationSucceeded : kVerificationFailed;
|
||||||
|
if (_verifyState == kVerificationFailed) {
|
||||||
|
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // close switch
|
} // close switch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::challengeOwner() {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
QByteArray avatarID = ("{" + _marketplaceIdFromURL + "}").toUtf8();
|
||||||
|
QByteArray nonce = QUuid::createUuid().toByteArray();
|
||||||
|
|
||||||
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||||
|
2 * sizeof(int) + nonce.length() + avatarID.length(), true);
|
||||||
|
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
||||||
|
challengeOwnershipPacket->writePrimitive(nonce.length());
|
||||||
|
challengeOwnershipPacket->write(avatarID);
|
||||||
|
challengeOwnershipPacket->write(nonce);
|
||||||
|
|
||||||
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
||||||
|
_challengeNonce = nonce;
|
||||||
|
|
||||||
|
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||||
|
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||||
|
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
|
||||||
|
_verifyState = kVerificationFailed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::handleChallengeResponse(ReceivedMessage * response) {
|
||||||
|
QByteArray avatarID;
|
||||||
|
QByteArray encryptedNonce;
|
||||||
|
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||||
|
if (_verifyState == kChallengeClient) {
|
||||||
|
_challengeTimeout.stop();
|
||||||
|
_challengeResponse = response->readAll();
|
||||||
|
_verifyState = kChallengeResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,13 +27,14 @@ public:
|
||||||
void fetchAvatarFST();
|
void fetchAvatarFST();
|
||||||
bool isCertifyFailed() const { return _verifyState == kVerificationFailed; }
|
bool isCertifyFailed() const { return _verifyState == kVerificationFailed; }
|
||||||
void processCertifyEvents();
|
void processCertifyEvents();
|
||||||
|
void handleChallengeResponse(ReceivedMessage * response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _needsHeroCheck{ false };
|
bool _needsHeroCheck{ false };
|
||||||
|
|
||||||
// Avatar certification/verification:
|
// Avatar certification/verification:
|
||||||
enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse,
|
enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse,
|
||||||
kChallengeClient, kVerified, kVerificationFailed, kVerificationSucceeded, kError };
|
kChallengeClient, kChallengeResponse, kVerified, kVerificationFailed, kVerificationSucceeded, kError };
|
||||||
Q_ENUM(VerifyState);
|
Q_ENUM(VerifyState);
|
||||||
VerifyState _verifyState { kNoncertified };
|
VerifyState _verifyState { kNoncertified };
|
||||||
QMutex _avatarCertifyLock;
|
QMutex _avatarCertifyLock;
|
||||||
|
@ -44,10 +45,15 @@ private:
|
||||||
QString _certificateIdFromURL;
|
QString _certificateIdFromURL;
|
||||||
QString _certificateIdFromFST;
|
QString _certificateIdFromFST;
|
||||||
QString _dynamicMarketResponse;
|
QString _dynamicMarketResponse;
|
||||||
|
QString _ownerPublicKey;
|
||||||
|
QByteArray _challengeNonce;
|
||||||
|
QByteArray _challengeResponse;
|
||||||
|
QTimer _challengeTimeout;
|
||||||
|
|
||||||
bool generateFSTHash();
|
bool generateFSTHash();
|
||||||
bool validateFSTHash(const QString& publicKey);
|
bool validateFSTHash(const QString& publicKey);
|
||||||
QByteArray canonicalJson(const QString fstFile);
|
QByteArray canonicalJson(const QString fstFile);
|
||||||
|
void challengeOwner();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void fstRequestComplete();
|
void fstRequestComplete();
|
||||||
|
|
Loading…
Reference in a new issue