diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c13de0449e..4e5b563fc6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -579,7 +579,6 @@ const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer void DomainServer::processConnectRequestPacket(QSharedPointer<NLPacket> packet) { NodeType_t nodeType; HifiSockAddr publicSockAddr, localSockAddr; - if (packet->getPayloadSize() == 0) { return; @@ -625,31 +624,62 @@ void DomainServer::processConnectRequestPacket(QSharedPointer<NLPacket> packet) return; } } - } QList<NodeType_t> nodeInterestList; QString username; QByteArray usernameSignature; - - packetStream >> nodeInterestList >> username >> usernameSignature; - + auto limitedNodeList = DependencyManager::get<LimitedNodeList>(); - + + packetStream >> nodeInterestList; + + if (packet->bytesLeftToRead() > 0) { + // try to verify username + packetStream >> username; + } + + bool isRestrictingAccess = + _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + + // we always let in a user who is sending a packet from our local socket or from the localhost address + bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost); + + if (isRestrictingAccess && !isLocalUser) { + if (!username.isEmpty()) { + // if there's a username, try to unpack username signature + packetStream >> usernameSignature; + + if (usernameSignature.isEmpty()) { + // if user didn't include usernameSignature in connect request, send a connectionToken packet + QUuid& connectionToken = _connectionTokenHash[username.toLower()]; + + if (connectionToken.isNull()) { + connectionToken = QUuid::createUuid(); + } + + static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID); + connectionTokenPacket->reset(); + connectionTokenPacket->write(connectionToken.toRfc4122()); + limitedNodeList->sendUnreliablePacket(*connectionTokenPacket, packet->getSenderSockAddr()); + return; + } + } + } + QString reason; if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr, reason)) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection - QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); - connectionDeniedPacket->writePrimitive(payloadSize); - connectionDeniedPacket->write(utfString); - + if (payloadSize > 0) { + connectionDeniedPacket->writePrimitive(payloadSize); + connectionDeniedPacket->write(utfString); + } // tell client it has been refused. limitedNodeList->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); - return; } @@ -736,6 +766,7 @@ void DomainServer::processConnectRequestPacket(QSharedPointer<NLPacket> packet) } } + void DomainServer::processListRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) { NodeType_t throwawayNodeType; @@ -766,52 +797,61 @@ unsigned int DomainServer::countConnectedUsers() { } -bool DomainServer::verifyUsersKey(const QString& username, - const QByteArray& usernameSignature, - QString& reasonReturn) { +bool DomainServer::verifyUserSignature(const QString& username, + const QByteArray& usernameSignature, + QString& reasonReturn) { + // it's possible this user can be allowed to connect, but we need to check their username signature - QByteArray publicKeyArray = _userPublicKeys.value(username); - if (!publicKeyArray.isEmpty()) { + + const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); + + if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { // if we do have a public key for the user, check for a signature match const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData()); // first load up the public key into an RSA struct RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); - + + QByteArray lowercaseUsername = username.toLower().toUtf8(); + QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), + QCryptographicHash::Sha256); + if (rsaPublicKey) { QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); - int decryptResult = - RSA_public_decrypt(usernameSignature.size(), - reinterpret_cast<const unsigned char*>(usernameSignature.constData()), - reinterpret_cast<unsigned char*>(decryptedArray.data()), - rsaPublicKey, RSA_PKCS1_PADDING); + int decryptResult = RSA_verify(NID_sha256, + reinterpret_cast<const unsigned char*>(usernameWithToken.constData()), + usernameWithToken.size(), + reinterpret_cast<const unsigned char*>(usernameSignature.constData()), + usernameSignature.size(), + rsaPublicKey); + + if (decryptResult == 1) { + qDebug() << "Username signature matches for" << username << "- allowing connection."; - if (decryptResult != -1) { - if (username.toLower() == decryptedArray) { - qDebug() << "Username signature matches for" << username << "- allowing connection."; + // free up the public key and remove connection token before we return + RSA_free(rsaPublicKey); + _connectionTokenHash.remove(username); - // free up the public key before we return - RSA_free(rsaPublicKey); - - return true; - } else { - qDebug() << "Username signature did not match for" << username << "- denying connection."; - reasonReturn = "Username signature did not match."; - } + return true; + } else { - qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection."; - reasonReturn = "Couldn't decrypt user signature."; + qDebug() << "Error decrypting username signature for " << username << "- denying connection."; + reasonReturn = "Error decrypting username signature."; + // free up the public key, we don't need it anymore + RSA_free(rsaPublicKey); } - // free up the public key, we don't need it anymore - RSA_free(rsaPublicKey); } else { + // we can't let this user in since we couldn't convert their public key to an RSA key we could use qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; reasonReturn = "Couldn't convert data to RSA key."; } + } else { + qDebug() << "Insufficient data to decrypt username signature - denying connection."; + reasonReturn = "Insufficient data"; } requestUserPublicKey(username); // no joy. maybe next time? @@ -823,41 +863,40 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr, QString& reasonReturn) { - + + //TODO: improve flow so these bools aren't declared twice bool isRestrictingAccess = _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost); - // we always let in a user who is sending a packet from our local socket or from the localhost address - if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() - || senderSockAddr.getAddress() == QHostAddress::LocalHost) { - return true; - } - - if (isRestrictingAccess) { - + if (isRestrictingAccess && !isLocalUser) { QStringList allowedUsers = _settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); - + if (allowedUsers.contains(username, Qt::CaseInsensitive)) { - if (!verifyUsersKey(username, usernameSignature, reasonReturn)) { + if (username.isEmpty()) { + qDebug() << "Connect request denied - no username provided."; + reasonReturn = "No username provided"; + return false; + } + if (!verifyUserSignature(username, usernameSignature, reasonReturn)) { return false; } } else { qDebug() << "Connect request denied for user" << username << "not in allowed users list."; reasonReturn = "User not on whitelist."; - + return false; } } - + // either we aren't restricting users, or this user is in the allowed list - // if this user is in the editors list, exempt them from the max-capacity check const QVariant* allowedEditorsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); if (allowedEditors.contains(username)) { - if (verifyUsersKey(username, usernameSignature, reasonReturn)) { + if (verifyUserSignature(username, usernameSignature, reasonReturn)) { return true; } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 7786fb34ac..7495e080de 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -90,7 +90,7 @@ private: void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); unsigned int countConnectedUsers(); - bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn); + bool verifyUserSignature (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn); bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr, QString& reasonReturn); @@ -149,6 +149,8 @@ private: QSet<QUuid> _webAuthenticationStateSet; QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash; + + QHash<QString, QUuid> _connectionTokenHash; QHash<QString, QByteArray> _userPublicKeys; diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index a85aa588a8..0628e21574 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -10,11 +10,13 @@ // #include <openssl/rsa.h> +#include <openssl/x509.h> #include <qjsondocument.h> #include <QtCore/QDebug> #include <QtCore/QDataStream> +#include "UUID.h" #include "NetworkLogging.h" #include "DataServerAccountInfo.h" @@ -30,8 +32,7 @@ DataServerAccountInfo::DataServerAccountInfo() : _walletID(), _balance(0), _hasBalance(false), - _privateKey(), - _usernameSignature() + _privateKey() { } @@ -73,9 +74,6 @@ void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject void DataServerAccountInfo::setUsername(const QString& username) { if (_username != username) { _username = username; - - // clear our username signature so it has to be re-created - _usernameSignature = QByteArray(); qCDebug(networking) << "Username changed to" << username; } @@ -128,8 +126,8 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject setWalletID(QUuid(user["wallet_id"].toString())); } -const QByteArray& DataServerAccountInfo::getUsernameSignature() { - if (_usernameSignature.isEmpty()) { +QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { + if (!_privateKey.isEmpty()) { const char* privateKeyData = _privateKey.constData(); RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, @@ -137,36 +135,41 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() { _privateKey.size()); if (rsaPrivateKey) { QByteArray lowercaseUsername = _username.toLower().toUtf8(); - _usernameSignature.resize(RSA_size(rsaPrivateKey)); + QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), + QCryptographicHash::Sha256); - int encryptReturn = RSA_private_encrypt(lowercaseUsername.size(), - reinterpret_cast<const unsigned char*>(lowercaseUsername.constData()), - reinterpret_cast<unsigned char*>(_usernameSignature.data()), - rsaPrivateKey, RSA_PKCS1_PADDING); + QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0); + unsigned int usernameSignatureSize = 0; - if (encryptReturn == -1) { - qCDebug(networking) << "Error encrypting username signature."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - _usernameSignature = QByteArray(); - } + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast<const unsigned char*>(usernameWithToken.constData()), + usernameWithToken.size(), + reinterpret_cast<unsigned char*>(usernameSignature.data()), + &usernameSignatureSize, + rsaPrivateKey); // free the private key RSA struct now that we are done with it RSA_free(rsaPrivateKey); + + 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 _usernameSignature; + return QByteArray(); } void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; - // clear our username signature so it has to be re-created - _usernameSignature = QByteArray(); } QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index d7f9d5167a..9b80de5422 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -43,7 +43,7 @@ public: const QUuid& getWalletID() const { return _walletID; } void setWalletID(const QUuid& walletID); - const QByteArray& getUsernameSignature(); + QByteArray getUsernameSignature(const QUuid& connectionToken); bool hasPrivateKey() const { return !_privateKey.isEmpty(); } void setPrivateKey(const QByteArray& privateKey); @@ -73,7 +73,7 @@ private: qint64 _balance; bool _hasBalance; QByteArray _privateKey; - QByteArray _usernameSignature; + }; #endif // hifi_DataServerAccountInfo_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index afb362053e..8f4b9cc61f 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -29,6 +29,7 @@ DomainHandler::DomainHandler(QObject* parent) : _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), + _connectionToken(), _iceDomainID(), _iceClientID(), _iceServerSockAddr(), @@ -43,7 +44,8 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); - + _connectionToken = QUuid(); + _icePeer.reset(); if (requiresICE()) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 6079035f8b..7bb0582914 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -50,9 +50,12 @@ public: unsigned short getPort() const { return _sockAddr.getPort(); } void setPort(quint16 port) { _sockAddr.setPort(port); } + const QUuid& getConnectionToken() const { return _connectionToken; } + void setConnectionToken(const QUuid& connectionToken) { _connectionToken = connectionToken; } + const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - + const QUuid& getICEDomainID() const { return _iceDomainID; } const QUuid& getICEClientID() const { return _iceClientID; } @@ -114,6 +117,7 @@ private: QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; + QUuid _connectionToken; QUuid _iceDomainID; QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index dfa3ef86a5..44b9c0b829 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -94,7 +94,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::PingReply, this, "processPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); - + packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); @@ -274,17 +274,23 @@ 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()) { + if (!_domainHandler.isConnected() ) { + DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); packetStream << accountInfo.getUsername(); - - const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(); - - if (!usernameSignature.isEmpty()) { - qCDebug(networking) << "Including username signature in domain connect request."; - packetStream << usernameSignature; + + // 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; + } } } @@ -361,6 +367,7 @@ void NodeList::sendDSPathQuery(const QString& newPath) { } } + void NodeList::processDomainServerPathResponse(QSharedPointer<NLPacket> packet) { // This is a response to a path query we theoretically made. // In the future we may want to check that this was actually from our DS and for a query we actually made. @@ -450,6 +457,17 @@ void NodeList::pingPunchForDomainServer() { } } + +void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer<NLPacket> packet) { + if (_domainHandler.getSockAddr().isNull()) { + // refuse to process this packet if we aren't currently connected to the DS + return; + } + // read in the connection token from the packet, then send domain-server checkin + _domainHandler.setConnectionToken(QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID))); + sendDomainServerCheckIn(); +} + void NodeList::processDomainServerList(QSharedPointer<NLPacket> packet) { if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index e3f8feeeda..3aae3e3dfc 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -76,6 +76,8 @@ public slots: void processDomainServerAddedNode(QSharedPointer<NLPacket> packet); void processDomainServerPathResponse(QSharedPointer<NLPacket> packet); + void processDomainServerConnectionTokenPacket(QSharedPointer<NLPacket> packet); + void processPingPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode); void processPingReplyPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0591ac30fe..f3f222f310 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -26,7 +26,7 @@ const QSet<PacketType::Value> SEQUENCE_NUMBERED_PACKETS = QSet<PacketType::Value const QSet<PacketType::Value> NON_SOURCED_PACKETS = QSet<PacketType::Value>() << StunResponse << CreateAssignment << RequestAssignment - << DomainServerRequireDTLS << DomainConnectRequest + << DomainServerRequireDTLS << DomainConnectRequest << DomainServerConnectionToken << DomainList << DomainConnectionDenied << DomainServerPathQuery << DomainServerPathResponse << DomainServerAddedNode @@ -119,6 +119,7 @@ QString nameForPacketType(PacketType::Value packetType) { PACKET_TYPE_NAME_LOOKUP(ICEPingReply); PACKET_TYPE_NAME_LOOKUP(EntityAdd); PACKET_TYPE_NAME_LOOKUP(EntityEdit); + PACKET_TYPE_NAME_LOOKUP(DomainServerConnectionToken); default: return QString("Type: ") + QString::number((int)packetType); } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3f3f165e87..46f834db74 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -73,7 +73,8 @@ namespace PacketType { EntityQuery, EntityAdd, EntityErase, - EntityEdit + EntityEdit, + DomainServerConnectionToken }; };