Implement static cert verification correctly

This commit is contained in:
Zach Fox 2017-10-19 15:30:49 -07:00
parent 0dc6211a7d
commit 74180bc4cd
6 changed files with 49 additions and 75 deletions

View file

@ -459,9 +459,7 @@ void EntityServer::startDynamicDomainVerification() {
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
if (entity) {
// ZRF FIXME!!!
//if (!entity->verifyStaticCertificateProperties()) {
if (false) {
if (!entity->verifyStaticCertificateProperties()) {
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
<< "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification

View file

@ -14,9 +14,13 @@
#include <QtCore/QObject>
#include <QtEndian>
#include <QJsonDocument>
#include <openssl/rsa.h> // see comments for DEBUG_CERT
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <glm/gtx/transform.hpp>
@ -41,6 +45,7 @@ int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
QString EntityItem::_marketplacePublicKey;
EntityItem::EntityItem(const EntityItemID& entityItemID) :
SpatiallyNestable(NestableType::Entity, entityItemID)
@ -1588,16 +1593,16 @@ QByteArray EntityItem::getStaticCertificateJSON() const {
// It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically
// to help maintainence in two different code bases.
if (!propertySet.getAnimation().getURL().isEmpty()) {
json["animation.url"] = propertySet.getAnimation().getURL();
json["animationURL"] = propertySet.getAnimation().getURL();
}
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
ADD_INT_PROPERTY(editionNumber, EditionNumber);
ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber);
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
ADD_STRING_PROPERTY(itemLicense, ItemLicense);
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
ADD_STRING_PROPERTY(itemName, ItemName);
ADD_INT_PROPERTY(limitedRun, LimitedRun);
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
@ -1612,39 +1617,6 @@ QByteArray EntityItem::getStaticCertificateHash() const {
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
}
#ifdef DEBUG_CERT
QString EntityItem::computeCertificateID() {
// Until the marketplace generates it, compute and answer the certificateID here.
// Does not set it, as that will have to be done from script engine in order to update server, etc.
const auto hash = getStaticCertificateHash();
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
const unsigned int textLength = hash.length();
const char privateKey[] = "-----BEGIN RSA PRIVATE KEY-----\n\
MIIBOQIBAAJBALCoBiDAZOClO26tC5pd7JikBL61WIgpAqbcNnrV/TcG6LPI7Zbi\n\
MjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQJABvOlwhYwIhL+gr12jm2R\n\
yPPzZ9nVEQ6kFxLlZfIT09119fd6OU1X5d4sHWfMfSIEgjwQIDS3ZU1kY3XKo87X\n\
zQIhAOPHlYa1OC7BLhaTouy68qIU2vCKLP8mt4S31/TT0UOnAiEAxor6gU6yupTQ\n\
yuyV3yHvr5LkZKBGqhjmOTmDfgtX7ncCIChGbgX3nQuHVOLhD/nTxHssPNozVGl5\n\
KxHof+LmYSYZAiB4U+yEh9SsXdq40W/3fpLMPuNq1PRezJ5jGidGMcvF+wIgUNec\n\
3Kg2U+CVZr8/bDT/vXRrsKj1zfobYuvbfVH02QY=\n\
-----END RSA PRIVATE KEY-----";
BIO* bio = BIO_new_mem_buf((void*)privateKey, sizeof(privateKey));
RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
QByteArray signature(RSA_size(rsa), 0);
unsigned int signatureLength = 0;
const int signOK = RSA_sign(NID_sha256, text, textLength, reinterpret_cast<unsigned char*>(signature.data()), &signatureLength, rsa);
BIO_free(bio);
RSA_free(rsa);
if (!signOK) {
qCWarning(entities) << "Unable to compute signature for" << getName() << getEntityItemID();
return "";
}
return signature.toBase64();
#endif
}
bool EntityItem::verifyStaticCertificateProperties() {
// True IIF a non-empty certificateID matches the static certificate json.
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
@ -1659,13 +1631,8 @@ bool EntityItem::verifyStaticCertificateProperties() {
const auto hash = getStaticCertificateHash();
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
const unsigned int textLength = hash.length();
// After DEBUG_CERT ends, we will get/cache this once from the marketplace when needed, and it likely won't be RSA.
const char publicKey[] = "-----BEGIN PUBLIC KEY-----\n\
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALCoBiDAZOClO26tC5pd7JikBL61WIgp\n\
AqbcNnrV/TcG6LPI7ZbiMjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQ==\n\
-----END PUBLIC KEY-----";
BIO *bio = BIO_new_mem_buf((void*)publicKey, sizeof(publicKey));
BIO *bio = BIO_new_mem_buf((void*)qPrintable(EntityItem::_marketplacePublicKey), -1);
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
bool answer = RSA_verify(NID_sha256, text, textLength, signature, signatureLength, rsa);
@ -3006,3 +2973,34 @@ void EntityItem::somethingChangedNotification() {
}
});
}
void EntityItem::retrieveMarketplacePublicKey() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
requestURL.setPath("/api/v1/commerce/marketplace_key");
QJsonObject request;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = NULL;
networkReply = networkAccessManager.get(networkRequest);
connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
if (!jsonObject["public_key"].toString().isEmpty()) {
EntityItem::_marketplacePublicKey = jsonObject["public_key"].toString();
qCWarning(entities) << "Marketplace public key has been set to" << _marketplacePublicKey;
} else {
qCWarning(entities) << "Marketplace public key is empty!";
}
} else {
qCWarning(entities) << "Call to" << networkRequest.url() << "failed! Error:" << networkReply->error();
}
networkReply->deleteLater();
});
}

View file

@ -36,9 +36,6 @@
#include "SimulationFlags.h"
#include "EntityDynamicInterface.h"
// FIXME: The server-side marketplace will soon create the certificateID. At that point, all of the DEBUG_CERT stuff will go away.
#define DEBUG_CERT 1
class EntitySimulation;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
@ -331,9 +328,6 @@ public:
QByteArray getStaticCertificateJSON() const;
QByteArray getStaticCertificateHash() const;
bool verifyStaticCertificateProperties();
#ifdef DEBUG_CERT
QString computeCertificateID();
#endif
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -484,6 +478,9 @@ public:
ChangeHandlerId registerChangeHandler(const ChangeHandlerCallback& handler);
void deregisterChangeHandler(const ChangeHandlerId& changeHandlerId);
static QString _marketplacePublicKey;
static void retrieveMarketplacePublicKey();
protected:
QHash<ChangeHandlerId, ChangeHandlerCallback> _changeHandlers;
@ -635,7 +632,6 @@ protected:
quint64 _lastUpdatedVelocityTimestamp { 0 };
quint64 _lastUpdatedAngularVelocityTimestamp { 0 };
quint64 _lastUpdatedAccelerationTimestamp { 0 };
};
#endif // hifi_EntityItem_h

View file

@ -1778,18 +1778,3 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
}
return result;
}
#ifdef DEBUG_CERT
QString EntityScriptingInterface::computeCertificateID(const QUuid& entityID) {
QString result { "" };
if (_entityTree) {
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
result = entity->computeCertificateID();
}
});
}
return result;
}
#endif

View file

@ -387,9 +387,6 @@ public slots:
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID);
#ifdef DEBUG_CERT
Q_INVOKABLE QString computeCertificateID(const QUuid& entityID);
#endif
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);

View file

@ -71,6 +71,8 @@ EntityTree::EntityTree(bool shouldReaverage) :
Octree(shouldReaverage)
{
resetClientEditStats();
EntityItem::retrieveMarketplacePublicKey();
}
EntityTree::~EntityTree() {
@ -1523,9 +1525,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
_totalCreates++;
if (newEntity && isCertified && getIsServer()) {
// ZRF FIXME!!!
//if (!newEntity->verifyStaticCertificateProperties()) {
if (false) {
if (!newEntity->verifyStaticCertificateProperties()) {
qCDebug(entities) << "User" << senderNode->getUUID()
<< "attempted to add a certified entity with ID" << entityItemID << "which failed"
<< "static certificate verification.";