Avatar-owner challenge now working

This commit is contained in:
Simon Walton 2019-04-17 18:33:30 -07:00
parent 1e7669dd0c
commit a3cd5ad3c5
4 changed files with 95 additions and 7 deletions

View file

@ -82,6 +82,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership");
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
@ -1123,6 +1124,16 @@ void AvatarMixer::entityChange() {
_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() {
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();

View file

@ -65,6 +65,7 @@ private slots:
void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void start();
private:

View file

@ -24,6 +24,15 @@
#include "MixerAvatar.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() {
_verifyState = kNoncertified;
_certificateIdFromURL.clear();
@ -137,17 +146,17 @@ bool MixerAvatar::generateFSTHash() {
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
// Guess we should refactor this stuff into a Authorization namespace ...
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
}
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
QStringList fstLines = fstFile.split("\n", QString::SkipEmptyParts);
static const QString fstKeywordsReg{
static const QString fstKeywordsReg {
"(marketplaceID|itemDescription|itemCategories|itemArtist|itemLicenseUrl|limitedRun|itemName|"
"filename|texdir|script|editionNumber|certificateID)"
};
QRegularExpression fstLineRegExp{ QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" };
QRegularExpression fstLineRegExp { QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" };
QStringListIterator fstLineIter(fstLines);
QJsonObject certifiedItems;
@ -193,7 +202,7 @@ QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
void MixerAvatar::processCertifyEvents() {
QMutexLocker certifyLocker(&_avatarCertifyLock);
if (_verifyState != kOwnerResponse) {
if (_verifyState != kOwnerResponse && _verifyState != kChallengeResponse) {
return;
}
@ -218,7 +227,10 @@ void MixerAvatar::processCertifyEvents() {
}
}
if (ownerValid && !ownerPublicKey.isEmpty()) {
// Challenge owner ...
_ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n"
+ ownerPublicKey
+ "\n-----END PUBLIC KEY-----\n";
challengeOwner();
} else {
_verifyState = kError;
}
@ -230,5 +242,63 @@ void MixerAvatar::processCertifyEvents() {
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
}
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;
}
}

View file

@ -27,13 +27,14 @@ public:
void fetchAvatarFST();
bool isCertifyFailed() const { return _verifyState == kVerificationFailed; }
void processCertifyEvents();
void handleChallengeResponse(ReceivedMessage * response);
private:
bool _needsHeroCheck{ false };
// Avatar certification/verification:
enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse,
kChallengeClient, kVerified, kVerificationFailed, kVerificationSucceeded, kError };
kChallengeClient, kChallengeResponse, kVerified, kVerificationFailed, kVerificationSucceeded, kError };
Q_ENUM(VerifyState);
VerifyState _verifyState { kNoncertified };
QMutex _avatarCertifyLock;
@ -44,10 +45,15 @@ private:
QString _certificateIdFromURL;
QString _certificateIdFromFST;
QString _dynamicMarketResponse;
QString _ownerPublicKey;
QByteArray _challengeNonce;
QByteArray _challengeResponse;
QTimer _challengeTimeout;
bool generateFSTHash();
bool validateFSTHash(const QString& publicKey);
QByteArray canonicalJson(const QString fstFile);
void challengeOwner();
private slots:
void fstRequestComplete();