From 50f27d3e16fad908e0c32d68b11280f2ffa72f6c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 14 Oct 2014 18:02:22 -0700 Subject: [PATCH] handle verification of username signature during connection --- domain-server/CMakeLists.txt | 8 ++ domain-server/src/DomainServer.cpp | 130 ++++++++++++------ domain-server/src/DomainServer.h | 2 + .../networking/src/DataServerAccountInfo.cpp | 3 + .../networking/src/RSAKeypairGenerator.cpp | 2 +- 5 files changed, 102 insertions(+), 43 deletions(-) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 17d8b61b11..24cb6e3fdc 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -38,4 +38,12 @@ endif () # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) +# find OpenSSL +find_package(OpenSSL REQUIRED) + +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}") + link_shared_dependencies() \ No newline at end of file diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 74c4ad097d..f65f196cf0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include #include #include @@ -558,48 +561,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock packetStream >> nodeInterestList >> username >> usernameSignature; - static const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), - ALLOWED_USERS_SETTINGS_KEYPATH); - static QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); - - if (!isAssignment && allowedUsers.count() > 0) { - // this is an agent, we need to ask them to provide us with their signed username to see if they are allowed in - // we always let in a user who is sending a packet from our local socket or from the localhost address - -// if (senderSockAddr.getAddress() != LimitedNodeList::getInstance()->getLocalSockAddr().getAddress() -// && senderSockAddr.getAddress() != QHostAddress::LocalHost) { - if (true) { - bool canConnect = false; - - if (allowedUsers.contains(username)) { - // it's possible this user can be allowed to connect, but we need to check their username signature - - // even if we have a public key for them right now, request a new one in case it has just changed - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; - - const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; - - qDebug() << "Requesting public key for user" << username; - - AccountManager::getInstance().unauthenticatedRequest(USER_PUBLIC_KEY_PATH.arg(username), - QNetworkAccessManager::GetOperation, callbackParams); - - if (_userPublicKeys.contains(username)) { - // if we do have a public key for the user, check for a signature match - } - } - - if (!canConnect) { - QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied); - - // send this oauth request datagram back to the client - LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr); - - return; - } - } + if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) { + // this is an agent and we've decided we won't let them connect - send them a packet to deny connection + QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied); + + // send this oauth request datagram back to the client + LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr); + + return; } if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) @@ -640,6 +609,83 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock } } +bool DomainServer::shouldAllowConnectionFromNode(const QString& username, + const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr) { + static const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), + ALLOWED_USERS_SETTINGS_KEYPATH); + static QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); + + if (allowedUsers.count() > 0) { + // this is an agent, we need to ask them to provide us with their signed username to see if they are allowed in + // we always let in a user who is sending a packet from our local socket or from the localhost address + + if (senderSockAddr.getAddress() != LimitedNodeList::getInstance()->getLocalSockAddr().getAddress() + && senderSockAddr.getAddress() != QHostAddress::LocalHost) { + if (allowedUsers.contains(username)) { + // 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()) { + // if we do have a public key for the user, check for a signature match + + const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData()); + + // first load up the public key into an RSA struct + RSA* rsaPublicKey = d2i_RSAPublicKey(NULL, &publicKeyData, publicKeyArray.size()); + + if (rsaPublicKey) { + QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); + int decryptResult = RSA_public_decrypt(usernameSignature.size(), + reinterpret_cast(usernameSignature.constData()), + reinterpret_cast(decryptedArray.data()), + rsaPublicKey, RSA_PKCS1_PADDING); + + if (decryptResult != -1) { + if (username == decryptedArray) { + qDebug() << "Username signature matches for" << username << "- allowing connection."; + + // free up the public key before we return + RSA_free(rsaPublicKey); + + return true; + } else { + qDebug() << "Username signature did not match for" << username << "- denying connection."; + } + } else { + qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection."; + } + + // 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."; + } + } + + // even if we have a public key for them right now, request a new one in case it has just changed + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; + + const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; + + qDebug() << "Requesting public key for user" << username; + + AccountManager::getInstance().unauthenticatedRequest(USER_PUBLIC_KEY_PATH.arg(username), + QNetworkAccessManager::GetOperation, callbackParams); + + } + } + } else { + // since we have no allowed user list, let them all in + return true; + } + + return false; +} + QUrl DomainServer::oauthRedirectURL() { return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort()); } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index d94c7157d1..1a402abada 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -83,6 +83,8 @@ private: void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr); + bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr); int parseNodeDataFromByteArray(QDataStream& packetStream, NodeType_t& nodeType, HifiSockAddr& publicSockAddr, diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 4a88186c96..61b1bbf418 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -143,6 +143,9 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() { qDebug() << "Will re-attempt on next domain-server check in."; _usernameSignature = QByteArray(); } + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); } else { qDebug() << "Could not create RSA struct from QByteArray private key."; qDebug() << "Will re-attempt on next domain-server check in."; diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index 91a7fe8df6..5b53a84dc4 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -51,7 +51,7 @@ void RSAKeypairGenerator::generateKeypair() { // grab the public key and private key from the file unsigned char* publicKeyDER = NULL; - int publicKeyLength = i2d_RSA_PUBKEY(keyPair, &publicKeyDER); + int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER); unsigned char* privateKeyDER = NULL; int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);