diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 31cab68cdf..aedf451924 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -236,10 +236,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) : { _averageLoopTime.updateAverage(0); qDebug() << "Octree server starting... [" << this << "]"; - - // make sure the AccountManager has an Auth URL for payment redemptions - - AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); } OctreeServer::~OctreeServer() { diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 7dc94421be..fae07ace45 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) { function createTemporaryDomain() { swal({ title: 'Create temporary place name', - text: "This will create a temporary place name and domain ID (valid for 30 days)" + text: "This will create a temporary place name and domain ID" + " so other users can easily connect to your domain.

" + "In order to make your domain reachable, this will also enable full automatic networking.", showCancelButton: true, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3e4ee7b758..f385f5c489 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -331,7 +331,6 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, QCryptographicHash::Sha256); if (rsaPublicKey) { - QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); int decryptResult = RSA_verify(NID_sha256, reinterpret_cast(usernameWithToken.constData()), usernameWithToken.size(), diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9e13c8e6fa..f0df67a6f6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) { + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { // we either read a certificate and private key or were not passed one // and completed login or did not need to @@ -198,7 +198,6 @@ bool DomainServer::optionallySetupOAuth() { } AccountManager& accountManager = AccountManager::getInstance(); - accountManager.disableSettingsFilePersistence(); accountManager.setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); @@ -372,20 +371,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); } -bool DomainServer::didSetupAccountManagerWithAccessToken() { - if (AccountManager::getInstance().hasValidAccessToken()) { - // we already gave the account manager a valid access token - return true; - } - - return resetAccountManagerAccessToken(); -} - const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; bool DomainServer::resetAccountManagerAccessToken() { @@ -401,9 +392,13 @@ bool DomainServer::resetAccountManagerAccessToken() { if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { - qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present." - << "Set an access token via the web interface, in your user or master config" + qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."; + qDebug() << "Set an access token via the web interface, in your user or master config" << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; + + // clear any existing access token from AccountManager + AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); + return false; } } else { @@ -429,34 +424,6 @@ bool DomainServer::resetAccountManagerAccessToken() { } } -bool DomainServer::optionallySetupAssignmentPayment() { - const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - - if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) && - settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() && - didSetupAccountManagerWithAccessToken()) { - - qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); - - // assume that the fact we are authing against HF data server means we will pay for assignments - // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* creditSetupTimer = new QTimer(this); - connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - - const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; - creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); - - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); - - const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); - } - - return true; -} - void DomainServer::setupAutomaticNetworking() { auto nodeList = DependencyManager::get(); @@ -467,9 +434,9 @@ void DomainServer::setupAutomaticNetworking() { setupICEHeartbeatForFullNetworking(); } - if (!didSetupAccountManagerWithAccessToken()) { - qDebug() << "Cannot send heartbeat to data server without an access token."; - qDebug() << "Add an access token to your config file or via the web interface."; + if (!resetAccountManagerAccessToken()) { + qDebug() << "Will not send heartbeat to Metaverse API without an access token."; + qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; return; } @@ -526,6 +493,19 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { // we need this DS to know what our public IP is - start trying to figure that out now limitedNodeList->startSTUNPublicSocketUpdate(); + // to send ICE heartbeats we'd better have a private key locally with an uploaded public key + auto& accountManager = AccountManager::getInstance(); + auto domainID = accountManager.getAccountInfo().getDomainID(); + + // if we have an access token and we don't have a private key or the current domain ID has changed + // we should generate a new keypair + if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { + accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + } + + // hookup to the signal from account manager that tells us when keypair is available + connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); + if (!_iceHeartbeatTimer) { // setup a timer to heartbeat with the ice-server every so often _iceHeartbeatTimer = new QTimer { this }; @@ -1082,11 +1062,76 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } -// TODO: have data-web respond with ice-server hostname to use - void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { - DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); + + auto& accountManager = AccountManager::getInstance(); + auto limitedNodeList = DependencyManager::get(); + + if (!accountManager.getAccountInfo().hasPrivateKey()) { + qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; + qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; + + if (!limitedNodeList->getSessionUUID().isNull()) { + accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + } else { + qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; + } + + return; + } + + // NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with + // QDataStream and the possibility of IPv6 address for the sockets. + if (!_iceServerHeartbeatPacket) { + _iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); + } + + bool shouldRecreatePacket = false; + + if (_iceServerHeartbeatPacket->getPayloadSize() > 0) { + // if either of our sockets have changed we need to re-sign the heartbeat + // first read the sockets out from the current packet + _iceServerHeartbeatPacket->seek(0); + QDataStream heartbeatStream(_iceServerHeartbeatPacket.get()); + + QUuid senderUUID; + HifiSockAddr publicSocket, localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; + + if (senderUUID != limitedNodeList->getSessionUUID() + || publicSocket != limitedNodeList->getPublicSockAddr() + || localSocket != limitedNodeList->getLocalSockAddr()) { + shouldRecreatePacket = true; + } + } else { + shouldRecreatePacket = true; + } + + if (shouldRecreatePacket) { + // either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed + // and we need to make a new one + + // reset the position in the packet before writing + _iceServerHeartbeatPacket->reset(); + + // write our plaintext data to the packet + QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get()); + heartbeatDataStream << limitedNodeList->getSessionUUID() + << limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr(); + + // setup a QByteArray that points to the plaintext data + auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize()); + + // generate a signature for the plaintext data in the packet + auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); + + // pack the signature with the data + heartbeatDataStream << signature; + } + + // send the heartbeat packet to the ice server now + limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket); } } @@ -1970,3 +2015,31 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer message) { + static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3; + + static int numHeartbeatDenials = 0; + if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) { + qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server" + << "- re-generating keypair now"; + + // we've hit our threshold of heartbeat denials, trigger a keypair re-generation + auto limitedNodeList = DependencyManager::get(); + AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + + // reset our number of heartbeat denials + numHeartbeatDenials = 0; + } +} + +void DomainServer::handleKeypairChange() { + if (_iceServerHeartbeatPacket) { + // reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated + // the next time we go to send an ice-server heartbeat + _iceServerHeartbeatPacket->setPayloadSize(0); + + // send a heartbeat to the ice server immediately + sendHeartbeatToIceServer(); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 326ca3e1a8..3a83e8696b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -61,6 +61,7 @@ public slots: void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); void processNodeDisconnectRequestPacket(QSharedPointer message); + void processICEServerHeartbeatDenialPacket(QSharedPointer message); private slots: void aboutToQuit(); @@ -78,16 +79,16 @@ private slots: void handleTempDomainError(QNetworkReply& requestReply); void queuedQuit(QString quitMessage, int exitCode); + + void handleKeypairChange(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - bool optionallySetupAssignmentPayment(); void optionallyGetTemporaryName(const QStringList& arguments); - bool didSetupAccountManagerWithAccessToken(); bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); @@ -153,6 +154,7 @@ private: DomainServerSettingsManager _settingsManager; HifiSockAddr _iceServerSocket; + std::unique_ptr _iceServerHeartbeatPacket; QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index cfec3c966c..e5bdffe2e2 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -6,3 +6,17 @@ setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) package_libraries_for_deployment() + +# find OpenSSL +find_package(OpenSSL REQUIRED) + +if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include") + # this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto + message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings." + "\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.") +endif () + +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 2baa7a13a7..f38923b873 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,14 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "IceServer.h" + +#include +#include + +#include +#include +#include +#include #include +#include #include #include -#include "IceServer.h" - const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000; const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; @@ -45,7 +52,6 @@ IceServer::IceServer(int argc, char* argv[]) : QTimer* inactivePeerTimer = new QTimer(this); connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); - } bool IceServer::packetVersionMatch(const udt::Packet& packet) { @@ -70,9 +76,14 @@ void IceServer::processPacket(std::unique_ptr packet) { if (nlPacket->getType() == PacketType::ICEServerHeartbeat) { SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket); - - // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now - peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + if (peer) { + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + } else { + // we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation + static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied); + _serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr()); + } } else if (nlPacket->getType() == PacketType::ICEServerQuery) { QDataStream heartbeatStream(nlPacket.get()); @@ -114,31 +125,135 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { // pull the UUID, public and private sock addrs for this peer QUuid senderUUID; HifiSockAddr publicSocket, localSocket; + QByteArray signature; QDataStream heartbeatStream(&packet); - - heartbeatStream >> senderUUID; - heartbeatStream >> publicSocket >> localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; - // make sure we have this sender in our peer hash - SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); + auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos()); + heartbeatStream >> signature; - if (!matchingPeer) { - // if we don't have this sender we need to create them now - matchingPeer = QSharedPointer::create(senderUUID, publicSocket, localSocket); - _activePeers.insert(senderUUID, matchingPeer); + // make sure this is a verified heartbeat before performing any more processing + if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) { + // make sure we have this sender in our peer hash + SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); - qDebug() << "Added a new network peer" << *matchingPeer; + if (!matchingPeer) { + // if we don't have this sender we need to create them now + matchingPeer = QSharedPointer::create(senderUUID, publicSocket, localSocket); + _activePeers.insert(senderUUID, matchingPeer); + + qDebug() << "Added a new network peer" << *matchingPeer; + } else { + // we already had the peer so just potentially update their sockets + matchingPeer->setPublicSocket(publicSocket); + matchingPeer->setLocalSocket(localSocket); + } + + // update our last heard microstamp for this network peer to now + matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + + return matchingPeer; } else { - // we already had the peer so just potentially update their sockets - matchingPeer->setPublicSocket(publicSocket); - matchingPeer->setLocalSocket(localSocket); + // not verified, return the empty peer object + return SharedNetworkPeer(); + } +} + +bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) { + // check if we have a private key for this domain ID - if we do not then fire off the request for it + auto it = _domainPublicKeys.find(domainID); + if (it != _domainPublicKeys.end()) { + + // attempt to verify the signature for this heartbeat + const unsigned char* publicKeyData = reinterpret_cast(it->second.constData()); + + // first load up the public key into an RSA struct + RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size()); + + if (rsaPublicKey) { + auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); + int verificationResult = RSA_verify(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.constData()), + signature.size(), + rsaPublicKey); + + // free up the public key and remove connection token before we return + RSA_free(rsaPublicKey); + + if (verificationResult == 1) { + // this is the only success case - we return true here to indicate that the heartbeat is verified + return true; + } else { + qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API."; + } + + } else { + // we can't let this user in since we couldn't convert their public key to an RSA key we could use + qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key."; + qWarning() << "Re-requesting public key from API"; + } } - // update our last heard microstamp for this network peer to now - matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + // we could not verify this heartbeat (missing public key, could not load public key, bad actor) + // ask the metaverse API for the right public key and return false to indicate that this is not verified + requestDomainPublicKey(domainID); - return matchingPeer; + return false; +} + +void IceServer::requestDomainPublicKey(const QUuid& domainID) { + // send a request to the metaverse API for the public key for this domain + QNetworkAccessManager* manager = new QNetworkAccessManager { this }; + connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); + + QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL }; + QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID)); + publicKeyURL.setPath(publicKeyPath); + + QNetworkRequest publicKeyRequest { publicKeyURL }; + publicKeyRequest.setAttribute(QNetworkRequest::User, domainID); + + qDebug() << "Requesting public key for domain with ID" << domainID; + + manager->get(publicKeyRequest); +} + +void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { + // get the domain ID from the QNetworkReply attribute + QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid(); + + if (reply->error() == QNetworkReply::NoError) { + // pull out the public key and store it for this domain + + // the response should be JSON + QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll()); + + static const QString DATA_KEY = "data"; + static const QString PUBLIC_KEY_KEY = "public_key"; + static const QString STATUS_KEY = "status"; + static const QString SUCCESS_VALUE = "success"; + + auto responseObject = responseDocument.object(); + if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) { + auto dataObject = responseObject[DATA_KEY].toObject(); + if (dataObject.contains(PUBLIC_KEY_KEY)) { + _domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8()); + } else { + qWarning() << "There was no public key present in response for domain with ID" << domainID; + } + } else { + qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID; + } + + } else { + // there was a problem getting the public key for the domain + // log it since it will be re-requested on the next heartbeat + + qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString(); + } } void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index f1c2c06b65..81234b2c3c 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -16,13 +16,15 @@ #include #include +#include + #include #include #include #include #include -typedef QHash NetworkPeerHash; +class QNetworkReply; class IceServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT @@ -31,6 +33,7 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: void clearInactivePeers(); + void publicKeyReplyFinished(QNetworkReply* reply); private: bool packetVersionMatch(const udt::Packet& packet); void processPacket(std::unique_ptr packet); @@ -38,10 +41,19 @@ private: SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket); void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr); + bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature); + void requestDomainPublicKey(const QUuid& domainID); + QUuid _id; udt::Socket _serverSocket; + + using NetworkPeerHash = QHash; NetworkPeerHash _activePeers; + HTTPManager _httpManager; + + using DomainPublicKeyHash = std::unordered_map; + DomainPublicKeyHash _domainPublicKeys; }; #endif // hifi_IceServer_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 244a0a82cf..fe1e0bbbcd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -544,7 +544,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); - connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; @@ -581,6 +580,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token + accountManager.setIsAgent(true); accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); UserActivityLogger::getInstance().launch(applicationVersion()); @@ -889,9 +889,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : SpacemouseManager::getInstance().init(); #endif - auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); - // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, @@ -3955,29 +3952,10 @@ void Application::clearDomainOctreeDetails() { void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); clearDomainOctreeDetails(); - _domainConnectionRefusals.clear(); // disable physics until we have enough information about our new location to not cause craziness. _physicsEnabled = false; } -void Application::handleDomainConnectionDeniedPacket(QSharedPointer message) { - // Read deny reason from packet - quint16 reasonSize; - message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); - - // output to the log so the user knows they got a denied connection request - // and check and signal for an access token so that we can make sure they are logged in - qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason; - qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature."; - - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); - } - - AccountManager::getInstance().checkAndSignalForAccessToken(); -} void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; @@ -4517,33 +4495,6 @@ void Application::openUrl(const QUrl& url) { } } -void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { - // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed - const QString VOXEL_SETTINGS_KEY = "voxels"; - const QString PER_VOXEL_COST_KEY = "per-voxel-credits"; - const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits"; - const QString VOXEL_WALLET_UUID = "voxel-wallet"; - - const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject(); - - qint64 satoshisPerVoxel = 0; - qint64 satoshisPerMeterCubed = 0; - QUuid voxelWalletUUID; - - if (!domainSettingsObject.isEmpty()) { - float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble(); - float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble(); - - satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT); - satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT); - - voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString()); - } - - qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed"; - qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID; -} - void Application::loadDialog() { auto scriptEngines = DependencyManager::get(); QString fileNameString = OffscreenUi::getOpenFileName( diff --git a/interface/src/Application.h b/interface/src/Application.h index 852e3e86a6..c16175a333 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -227,7 +227,6 @@ signals: void svoImportRequested(const QString& url); void checkBackgroundDownloads(); - void domainConnectionRefused(const QString& reason); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); @@ -297,9 +296,6 @@ private slots: void activeChanged(Qt::ApplicationState state); - void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void handleDomainConnectionDeniedPacket(QSharedPointer message); - void notifyPacketVersionMismatch(); void loadSettings(); @@ -476,7 +472,6 @@ private: typedef bool (Application::* AcceptURLMethod)(const QString &); static const QHash _acceptedExtensions; - QList _domainConnectionRefusals; glm::uvec2 _renderResolution; int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index ce5facb580..f9cd7679ae 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -22,11 +22,8 @@ #include #include -#include "DataServerAccountInfo.h" #include "Menu.h" -Q_DECLARE_METATYPE(DataServerAccountInfo) - static const QString RUNNING_MARKER_FILENAME = "Interface.running"; void CrashHandler::checkForAndHandleCrash() { @@ -57,7 +54,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { layout->addWidget(label); QRadioButton* option1 = new QRadioButton("Reset all my settings"); - QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info."); + QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); QRadioButton* option3 = new QRadioButton("Continue with my current settings"); option3->setChecked(true); layout->addWidget(option1); @@ -79,7 +76,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { return CrashHandler::DELETE_INTERFACE_INI; } if (option2->isChecked()) { - return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO; + return CrashHandler::RETAIN_AVATAR_INFO; } } @@ -88,7 +85,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { } void CrashHandler::handleCrash(CrashHandler::Action action) { - if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { // CrashHandler::DO_NOTHING or unexpected value return; } @@ -101,18 +98,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { const QString DISPLAY_NAME_KEY = "displayName"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; - const QString ACCOUNTS_GROUP = "accounts"; QString displayName; QUrl fullAvatarURL; QString fullAvatarModelName; QUrl address; - QMap accounts; - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Read login and avatar info - - qRegisterMetaType("DataServerAccountInfo"); - qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Read avatar info // Location and orientation settings.beginGroup(ADDRESS_MANAGER_GROUP); @@ -125,13 +117,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, settings.allKeys()) { - accounts.insert(key, settings.value(key).value()); - } - settings.endGroup(); } // Delete Interface.ini @@ -140,8 +125,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Write login and avatar info + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Write avatar info // Location and orientation settings.beginGroup(ADDRESS_MANAGER_GROUP); @@ -154,13 +139,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, accounts.keys()) { - settings.setValue(key, QVariant::fromValue(accounts.value(key))); - } - settings.endGroup(); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index fc754cf1ac..61361b6107 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -25,7 +25,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_LOGIN_AND_AVATAR_INFO, + RETAIN_AVATAR_INFO, DO_NOTHING }; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index b8c82498a1..4c9c0df1cb 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -25,7 +25,7 @@ WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); - connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) { static const QMetaMethod svoImportRequestedSignal = diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 4ded2216d0..d0838c4a8f 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -12,10 +12,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -60,13 +62,12 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co updateReciever(updateReceiver), updateSlot(updateSlot) { + } AccountManager::AccountManager() : _authURL(), - _pendingCallbackMap(), - _accountInfo(), - _shouldPersistToSettingsFile(true) + _pendingCallbackMap() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -80,9 +81,6 @@ AccountManager::AccountManager() : qRegisterMetaType("QHttpMultiPart*"); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - // once we have a profile in account manager make sure we generate a new keypair - connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewKeypair); } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; @@ -93,16 +91,9 @@ void AccountManager::logout() { emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - if (_shouldPersistToSettingsFile) { - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(path).remove(); - - qCDebug(networking) << "Removed account info for" << _authURL << "from in-memory accounts and .ini file"; - } else { - qCDebug(networking) << "Cleared data server account info in account manager."; - } + + // remove this account from the account settings file + removeAccountFromFile(); emit logoutComplete(); // the username has changed to blank @@ -124,35 +115,83 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) { emit balanceChanged(newBalance); } +QString accountFilePath() { + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/AccountInfo.bin"; +} + +QVariantMap accountMapFromFile(bool& success) { + QFile accountFile { accountFilePath() }; + + if (accountFile.open(QIODevice::ReadOnly)) { + // grab the current QVariantMap from the settings file + QDataStream readStream(&accountFile); + QVariantMap accountMap; + + readStream >> accountMap; + + // close the file now that we have read the data + accountFile.close(); + + success = true; + + return accountMap; + } else { + // failed to open file, return empty QVariantMap + // there was only an error if the account file existed when we tried to load it + success = !accountFile.exists(); + + return QVariantMap(); + } +} + void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - if (_shouldPersistToSettingsFile) { + // check if there are existing access tokens to load from settings + QFile accountsFile { accountFilePath() }; + bool loadedMap = false; + auto accountsMap = accountMapFromFile(loadedMap); + + if (accountsFile.exists() && loadedMap) { + // pull out the stored account info and store it in memory + _accountInfo = accountsMap[_authURL.toString()].value(); + + qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString()); + } else { + // we didn't have a file - see if we can migrate old settings and store them in the new file + // check if there are existing access tokens to load from settings Settings settings; settings.beginGroup(ACCOUNTS_GROUP); - + foreach(const QString& key, settings.allKeys()) { // take a key copy to perform the double slash replacement QString keyCopy(key); QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//")); - + if (keyURL == _authURL) { // pull out the stored access token and store it in memory _accountInfo = settings.value(key).value(); - qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); - } + + qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString()) + << "from previous settings file"; } } + + if (_accountInfo.getAccessToken().token.isEmpty()) { + qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; + } else { + // persist the migrated settings to file + persistAccountToFile(); + } + } + + if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) { + // we are missing profile information, request it now + requestProfile(); } // tell listeners that the auth endpoint has changed @@ -299,9 +338,11 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received JSON response from data-server that has no matching callback."; + qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll()); } + + requestReply->deleteLater(); } } @@ -317,22 +358,69 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { _pendingCallbackMap.remove(requestReply); } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received error response from data-server that has no matching callback."; + qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString(); qCDebug(networking) << requestReply->readAll(); } + + requestReply->deleteLater(); } } -void AccountManager::persistAccountToSettings() { - if (_shouldPersistToSettingsFile) { - // store this access token into the local settings - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(path).set(QVariant::fromValue(_accountInfo)); +bool writeAccountMapToFile(const QVariantMap& accountMap) { + // re-open the file and truncate it + QFile accountFile { accountFilePath() }; + if (accountFile.open(QIODevice::WriteOnly)) { + QDataStream writeStream(&accountFile); + + // persist the updated account QVariantMap to file + writeStream << accountMap; + + // close the file with the newly persisted settings + accountFile.close(); + + return true; + } else { + return false; } } +void AccountManager::persistAccountToFile() { + + qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath(); + + bool wasLoaded = false; + auto accountMap = accountMapFromFile(wasLoaded); + + if (wasLoaded) { + // replace the current account information for this auth URL in the account map + accountMap[_authURL.toString()] = QVariant::fromValue(_accountInfo); + + // re-open the file and truncate it + if (writeAccountMapToFile(accountMap)) { + return; + } + } + + qCWarning(networking) << "Could not load accounts file - unable to persist account information to file."; +} + +void AccountManager::removeAccountFromFile() { + bool wasLoaded = false; + auto accountMap = accountMapFromFile(wasLoaded); + + if (wasLoaded) { + accountMap.remove(_authURL.toString()); + if (writeAccountMapToFile(accountMap)) { + qCDebug(networking) << "Removed account info for" << _authURL << "from settings file."; + return; + } + } + + qCWarning(networking) << "Count not load accounts file - unable to remove account information for" << _authURL + << "from settings file."; +} + bool AccountManager::hasValidAccessToken() { if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { @@ -359,16 +447,19 @@ bool AccountManager::checkAndSignalForAccessToken() { } void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) { - // clear our current DataServerAccountInfo - _accountInfo = DataServerAccountInfo(); - - // start the new account info with a new OAuthAccessToken + // replace the account info access token with a new OAuthAccessToken OAuthAccessToken newOAuthToken; newOAuthToken.token = accessToken; - - qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); + + if (!accessToken.isEmpty()) { + qCDebug(networking) << "Setting new AccountManager OAuth token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); + } else if (!_accountInfo.getAccessToken().token.isEmpty()) { + qCDebug(networking) << "Clearing AccountManager OAuth token."; + } _accountInfo.setAccessToken(newOAuthToken); + + persistAccountToFile(); } void AccountManager::requestAccessToken(const QString& login, const QString& password) { @@ -423,7 +514,7 @@ void AccountManager::requestAccessTokenFinished() { emit loginComplete(rootURL); - persistAccountToSettings(); + persistAccountToFile(); requestProfile(); } @@ -469,7 +560,7 @@ void AccountManager::requestProfileFinished() { emit usernameChanged(_accountInfo.getUsername()); // store the whole profile into the local settings - persistAccountToSettings(); + persistAccountToFile(); } else { // TODO: error handling @@ -482,57 +573,141 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { qCDebug(networking) << "AccountManager requestProfileError - " << error; } -void AccountManager::generateNewKeypair() { - // setup a new QThread to generate the keypair on, in case it takes a while - QThread* generateThread = new QThread(this); - generateThread->setObjectName("Account Manager Generator Thread"); - - // setup a keypair generator - RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator(); - - connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); - connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair); - connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, - this, &AccountManager::handleKeypairGenerationError); - connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); - connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); - - keypairGenerator->moveToThread(generateThread); - - qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair."; - generateThread->start(); +void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) { + + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID)); + return; + } + + if (!isUserKeypair && domainID.isNull()) { + qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; + return; + } + + // make sure we don't already have an outbound keypair generation request + if (!_isWaitingForKeypairResponse) { + _isWaitingForKeypairResponse = true; + + // clear the current private key + qDebug() << "Clearing current private key in DataServerAccountInfo"; + _accountInfo.setPrivateKey(QByteArray()); + + // setup a new QThread to generate the keypair on, in case it takes a while + QThread* generateThread = new QThread(this); + generateThread->setObjectName("Account Manager Generator Thread"); + + // setup a keypair generator + RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; + + if (!isUserKeypair) { + keypairGenerator->setDomainID(domainID); + _accountInfo.setDomainID(domainID); + } + + // start keypair generation when the thread starts + connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); + + // handle success or failure of keypair generation + connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair); + connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, + this, &AccountManager::handleKeypairGenerationError); + + connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); + connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); + + keypairGenerator->moveToThread(generateThread); + + qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair."; + generateThread->start(); + } } -void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) { +void AccountManager::processGeneratedKeypair() { - qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key."; - - // set the private key on our data-server account info - _accountInfo.setPrivateKey(privateKey); - persistAccountToSettings(); - - // upload the public key so data-web has an up-to-date key - const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; - - // setup a multipart upload to send up the public key - QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(publicKey); - - requestMultiPart->append(keyPart); - - sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), requestMultiPart); - - // get rid of the keypair generator now that we don't need it anymore - sender()->deleteLater(); + qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now."; + + RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); + + if (keypairGenerator) { + // hold the private key to later set our metaverse API account info if upload succeeds + _pendingPrivateKey = keypairGenerator->getPrivateKey(); + + // upload the public key so data-web has an up-to-date key + const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; + + QString uploadPath; + if (keypairGenerator->getDomainID().isNull()) { + uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; + } else { + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + } + + // setup a multipart upload to send up the public key + QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart keyPart; + keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + keyPart.setBody(keypairGenerator->getPublicKey()); + + requestMultiPart->append(keyPart); + + // setup callback parameters so we know once the keypair upload has succeeded or failed + JSONCallbackParameters callbackParameters; + callbackParameters.jsonCallbackReceiver = this; + callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; + callbackParameters.errorCallbackReceiver = this; + callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; + + sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, + callbackParameters, QByteArray(), requestMultiPart); + + keypairGenerator->deleteLater(); + } else { + qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator" + << "but the casted sender is NULL. Will not process generated keypair."; + } +} + +void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { + qDebug() << "Uploaded public key to Metaverse API. RSA keypair generation is completed."; + + // public key upload complete - store the matching private key and persist the account to settings + _accountInfo.setPrivateKey(_pendingPrivateKey); + _pendingPrivateKey.clear(); + persistAccountToFile(); + + // clear our waiting state + _isWaitingForKeypairResponse = false; + + emit newKeypair(); + + // delete the reply object now that we are done with it + reply.deleteLater(); +} + +void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) { + // the public key upload has failed + qWarning() << "Public key upload failed from AccountManager" << reply.errorString(); + + // we aren't waiting for a response any longer + _isWaitingForKeypairResponse = false; + + // clear our pending private key + _pendingPrivateKey.clear(); + + // delete the reply object now that we are done with it + reply.deleteLater(); } void AccountManager::handleKeypairGenerationError() { - // for now there isn't anything we do with this except get the worker thread to clean up + qCritical() << "Error generating keypair - this is likely to cause authentication issues."; + + // reset our waiting state for keypair response + _isWaitingForKeypairResponse = false; + sender()->deleteLater(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 719279b0cf..0ebefafbed 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -62,12 +62,12 @@ public: QHttpMultiPart* dataMultiPart = NULL, const QVariantMap& propertyMap = QVariantMap()); + void setIsAgent(bool isAgent) { _isAgent = isAgent; } + const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } - void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; } - bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -87,7 +87,9 @@ public slots: void logout(); void updateBalance(); void accountInfoBalanceChanged(qint64 newBalance); - void generateNewKeypair(); + void generateNewUserKeypair() { generateNewKeypair(); } + void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); } + signals: void authRequired(); void authEndpointChanged(); @@ -97,25 +99,36 @@ signals: void loginFailed(); void logoutComplete(); void balanceChanged(qint64 newBalance); + void newKeypair(); + private slots: void processReply(); void handleKeypairGenerationError(); - void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void processGeneratedKeypair(); + void publicKeyUploadSucceeded(QNetworkReply& reply); + void publicKeyUploadFailed(QNetworkReply& reply); + void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); + private: AccountManager(); - AccountManager(AccountManager const& other); // not implemented - void operator=(AccountManager const& other); // not implemented + AccountManager(AccountManager const& other) = delete; + void operator=(AccountManager const& other) = delete; - void persistAccountToSettings(); + void persistAccountToFile(); + void removeAccountFromFile(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); QUrl _authURL; + QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; - bool _shouldPersistToSettingsFile; + bool _isAgent { false }; + + bool _isWaitingForKeypairResponse { false }; + QByteArray _pendingPrivateKey; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 5d633a8df1..9455fb1b88 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,19 +25,6 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif -DataServerAccountInfo::DataServerAccountInfo() : - _accessToken(), - _username(), - _xmppPassword(), - _discourseApiKey(), - _walletID(), - _balance(0), - _hasBalance(false), - _privateKey() -{ - -} - DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -47,6 +34,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _balance = otherInfo._balance; _hasBalance = otherInfo._hasBalance; _privateKey = otherInfo._privateKey; + _domainID = otherInfo._domainID; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -66,6 +54,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_balance, otherInfo._balance); swap(_hasBalance, otherInfo._hasBalance); swap(_privateKey, otherInfo._privateKey); + swap(_domainID, otherInfo._domainID); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -128,59 +117,62 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject } QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { - - if (!_privateKey.isEmpty()) { - const char* privateKeyData = _privateKey.constData(); - RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, - reinterpret_cast(&privateKeyData), - _privateKey.size()); - if (rsaPrivateKey) { - QByteArray lowercaseUsername = _username.toLower().toUtf8(); - QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), - QCryptographicHash::Sha256); - - QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0); - unsigned int usernameSignatureSize = 0; - - int encryptReturn = RSA_sign(NID_sha256, - reinterpret_cast(usernameWithToken.constData()), - usernameWithToken.size(), - reinterpret_cast(usernameSignature.data()), - &usernameSignatureSize, - rsaPrivateKey); - - // free the private key RSA struct now that we are done with it - RSA_free(rsaPrivateKey); + auto lowercaseUsername = _username.toLower().toUtf8(); + auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122()); - if (encryptReturn == -1) { - qCDebug(networking) << "Error encrypting username signature."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } else { - qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); - return usernameSignature; - } - - } else { - qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } - } - return QByteArray(); + auto signature = signPlaintext(plaintext); + if (!signature.isEmpty()) { + qDebug(networking) << "Returning username" << _username + << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); + } else { + qCDebug(networking) << "Error signing username with connection token"; + qCDebug(networking) << "Will re-attempt on next domain-server check in."; + } + + return signature; } -void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) { - _privateKey = privateKey; - +QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { + if (!_privateKey.isEmpty()) { + const char* privateKeyData = _privateKey.constData(); + RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, + reinterpret_cast(&privateKeyData), + _privateKey.size()); + if (rsaPrivateKey) { + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + + QByteArray hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); + + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); + + if (encryptReturn != -1) { + return signature; + } + } else { + qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; + } + } + return QByteArray(); } QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey; + << info._walletID << info._privateKey << info._domainID; + return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey; + >> info._walletID >> info._privateKey >> info._domainID; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 9b80de5422..6223bc008e 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -23,7 +23,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT public: - DataServerAccountInfo(); + DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); @@ -42,10 +42,6 @@ public: const QUuid& getWalletID() const { return _walletID; } void setWalletID(const QUuid& walletID); - - QByteArray getUsernameSignature(const QUuid& connectionToken); - bool hasPrivateKey() const { return !_privateKey.isEmpty(); } - void setPrivateKey(const QByteArray& privateKey); qint64 getBalance() const { return _balance; } float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; } @@ -54,6 +50,15 @@ public: void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply); + QByteArray getUsernameSignature(const QUuid& connectionToken); + bool hasPrivateKey() const { return !_privateKey.isEmpty(); } + void setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; } + + QByteArray signPlaintext(const QByteArray& plaintext); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -70,8 +75,9 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - qint64 _balance; - bool _hasBalance; + qint64 _balance { 0 }; + bool _hasBalance { false }; + QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain QByteArray _privateKey; }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index bf27789c54..b2eb0cd680 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -92,7 +92,9 @@ void DomainHandler::softReset() { disconnect(); clearSettings(); - + + _connectionDenialsSinceKeypairRegen = 0; + // cancel the failure timeout for any pending requests for settings QMetaObject::invokeMethod(&_settingsTimer, "stop"); } @@ -108,6 +110,9 @@ void DomainHandler::hardReset() { _hostname = QString(); _sockAddr.clear(); + _hasCheckedForAccessToken = false; + _domainConnectionRefusals.clear(); + // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); } @@ -349,3 +354,35 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes emit icePeerSocketsReceived(); } } + +void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { + // Read deny reason from packet + quint16 reasonSize; + message->readPrimitive(&reasonSize); + QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + + // output to the log so the user knows they got a denied connection request + // and check and signal for an access token so that we can make sure they are logged in + qCWarning(networking) << "The domain-server denied a connection request: " << reason; + qCWarning(networking) << "Make sure you are logged in."; + + if (!_domainConnectionRefusals.contains(reason)) { + _domainConnectionRefusals.append(reason); + emit domainConnectionRefused(reason); + } + + auto& accountManager = AccountManager::getInstance(); + + if (!_hasCheckedForAccessToken) { + accountManager.checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } + + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager.generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 957f52144b..03141d8fef 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -92,6 +92,7 @@ public slots: void processICEPingReplyPacket(QSharedPointer message); void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); void processICEResponsePacket(QSharedPointer icePacket); + void processDomainServerConnectionDeniedPacket(QSharedPointer message); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); @@ -114,6 +115,8 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); + void domainConnectionRefused(QString reason); + private: void sendDisconnectPacket(); void hardReset(); @@ -131,6 +134,10 @@ private: QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + + QStringList _domainConnectionRefusals; + bool _hasCheckedForAccessToken { false }; + int _connectionDenialsSinceKeypairRegen { 0 }; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a3707d19ba..f236f9d596 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -902,10 +902,6 @@ void LimitedNodeList::updateLocalSockAddr() { } } -void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) { - sendPacketToIceServer(PacketType::ICEServerHeartbeat, iceServerSockAddr, _sessionUUID); -} - void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID) { sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index fcad23da8f..de110c7e7f 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -143,6 +143,7 @@ public: bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } + const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } void processKillNode(ReceivedMessage& message); @@ -161,7 +162,6 @@ public: std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); - void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 677a1ad1e6..02bb17e870 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -80,11 +80,16 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send a ping punch immediately connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); + auto &accountManager = AccountManager::getInstance(); + + // assume that we may need to send a new DS check in anytime a new keypair is generated + connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); + // clear out NodeList when login is finished - connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); + connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset); // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); @@ -105,6 +110,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); + packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket"); packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); @@ -265,6 +271,26 @@ void NodeList::sendDomainServerCheckIn() { } + // check if we're missing a keypair we need to verify ourselves with the domain-server + auto& accountManager = AccountManager::getInstance(); + const QUuid& connectionToken = _domainHandler.getConnectionToken(); + + // we assume that we're on the same box as the DS if it has the same local address and + // it didn't present us with a connection token to use for username signature + bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost + || (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull()); + + bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; + + if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) { + qWarning() << "A keypair is required to present a username signature to the domain-server" + << "but no keypair is present. Waiting for keypair generation to complete."; + accountManager.generateNewUserKeypair(); + + // don't send the check in packet - wait for the keypair first + return; + } + auto domainPacket = NLPacket::create(domainPacketType); QDataStream packetStream(domainPacket.get()); @@ -289,23 +315,15 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); - - // if this is a connect request, and we can present a username signature, send it along - if (!_domainHandler.isConnected() ) { - - DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); + + if (!_domainHandler.isConnected()) { + DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); packetStream << accountInfo.getUsername(); - - // get connection token from the domain-server - const QUuid& connectionToken = _domainHandler.getConnectionToken(); - - if (!connectionToken.isNull()) { - - const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(connectionToken); - - if (!usernameSignature.isEmpty()) { - packetStream << usernameSignature; - } + + // if this is a connect request, and we can present a username signature, send it along + if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { + const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); + packetStream << usernameSignature; } } diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index 53b9b27cc6..a98cf74564 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() { // we can cleanup the RSA struct before we continue on RSA_free(keyPair); - QByteArray publicKeyArray(reinterpret_cast(publicKeyDER), publicKeyLength); - QByteArray privateKeyArray(reinterpret_cast(privateKeyDER), privateKeyLength); + _publicKey = QByteArray { reinterpret_cast(publicKeyDER), publicKeyLength }; + _privateKey = QByteArray { reinterpret_cast(privateKeyDER), privateKeyLength }; // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); - emit generatedKeypair(publicKeyArray, privateKeyArray); + emit generatedKeypair(); } diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index dd90313625..36f4a9550b 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -12,17 +12,31 @@ #ifndef hifi_RSAKeypairGenerator_h #define hifi_RSAKeypairGenerator_h -#include +#include +#include class RSAKeypairGenerator : public QObject { Q_OBJECT public: RSAKeypairGenerator(QObject* parent = 0); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + + const QByteArray& getPublicKey() const { return _publicKey; } + const QByteArray& getPrivateKey() const { return _privateKey; } + public slots: void generateKeypair(); + signals: void errorGeneratingKeypair(); - void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void generatedKeypair(); + +private: + QUuid _domainID; + QByteArray _publicKey; + QByteArray _privateKey; }; -#endif // hifi_RSAKeypairGenerator_h \ No newline at end of file +#endif // hifi_RSAKeypairGenerator_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 27b74f270c..3ffbb867bf 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -34,7 +34,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken << PacketType::DomainSettingsRequest << PacketType::DomainSettings << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat - << PacketType::ICEPing << PacketType::ICEPingReply + << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; @@ -51,6 +51,8 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + case PacketType::ICEServerHeartbeat: + return 18; // ICE Server Heartbeat signing default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0f586018db..7840ecb17e 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -90,7 +90,8 @@ public: DomainServerRemovedNode, MessagesData, MessagesSubscribe, - MessagesUnsubscribe + MessagesUnsubscribe, + ICEServerHeartbeatDenied }; };