diff --git a/src/server/AuthorizationManager.cpp b/src/server/AuthorizationManager.cpp index 2a20164..3cb65fe 100644 --- a/src/server/AuthorizationManager.cpp +++ b/src/server/AuthorizationManager.cpp @@ -17,6 +17,14 @@ #include #include +//Stuff for OpenSSL to work +#include +#include +#include +#include +#include +#include + //Internal defines // -- token management #define TIMEOUTSECS 900 // (15 minutes) time before a token becomes invalid @@ -69,10 +77,10 @@ bool AuthorizationManager::hasFullAccess(QString token){ } //SSL Certificate register/revoke/list -bool AuthorizationManager::RegisterCertificate(QString token, QSslCertificate cert){ +bool AuthorizationManager::RegisterCertificate(QString token, QString pubkey, QString nickname, QString email){ if(!checkAuth(token)){ return false; } QString user = hashID(token).section("::::",2,2); //get the user name from the currently-valid token - CONFIG->setValue("RegisteredCerts/"+user+"/"+QString(cert.publicKey().toPem()), cert.toText()); + CONFIG->setValue("RegisteredCerts/"+user+"/"+pubkey, "Nickname: "+nickname+", Email: "+email); return true; } @@ -158,49 +166,6 @@ QString AuthorizationManager::LoginUP(QHostAddress host, QString user, QString p } } -QString AuthorizationManager::LoginUC(QHostAddress host, QString user, QList certs){ - //Login w/ username & SSL certificate - bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") ); - bool ok = false; - //First check that the user is valid on the system and part of the operator group - bool isOperator = false; - if(user!="root" && user!="toor"){ - QStringList groups = getUserGroups(user); - if(groups.contains("wheel")){ isOperator = true; } //full-access user - else if(!groups.contains("operator")){ - return ""; //user not allowed access if not in either of the wheel/operator groups - } - }else{ isOperator = true; } - //qDebug() << "Check username/certificate combination" << user << localhost; - - //Need to check the registered certificates for the designated user - if(!localhost || user=="root" || user=="toor"){ - for(int i=0; icontains("RegisteredCerts/"+user+"/"+QString(certs[i].publicKey().toPem()) ) ){ - //Cert was registered - check expiration info - // TO-DO - ok = true; - } - } - }else{ - ok = true; //allow local access for users without password - } - - qDebug() << "User Login Attempt:" << user << " Success:" << ok << " IP:" << host.toString(); - LogManager::log(LogManager::HOST, QString("User Login Attempt: ")+user+" Success: "+(ok?"true":"false")+" IP: "+host.toString() ); - if(!ok){ - //invalid login - //Bump the fail count for this host - bool overlimit = BumpFailCount(host.toString()); - if(overlimit){ emit BlockHost(host); } - return (overlimit ? "REFUSED" : ""); - }else{ - //valid login - generate a new token for it - ClearHostFail(host.toString()); - return generateNewToken(isOperator, user); - } -} - QString AuthorizationManager::LoginService(QHostAddress host, QString service){ bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") ); @@ -228,6 +193,80 @@ QString AuthorizationManager::LoginService(QHostAddress host, QString service){ }else{ return generateNewToken(false, service); }//services are never given operator privileges } +//Stage 1 SSL Login Check: Generation of random string for this user +QString AuthorizationManager::GenerateEncCheckString(){ + QString key; + for(int i=0; i HASH[pubkeys[i]]){ + //Note: normally only 1 request per user at a time, but it is possible for a couple different clients to try + // and authenticate as the same user (but different keys) at nearly the same time - so keep a short valid-string time frame (<30 seconds) + // to mitigate this possibility (need to prevent the second user-auth request from invalidating the first before the first auth handshake is finished) + HASH.remove(pubkeys[i]); //initstring expired - go ahead and remove it to reduce calc time later + } + } + //Now re-use the "pubkeys" variable for the public SSL keys + QString user; + pubkeys = CONFIG->allKeys().filter("RegisteredCerts/"); //Format: "RegisteredCerts//" + for(int i=0; i certs); //Login w/ username & SSL certificate QString LoginService(QHostAddress host, QString service); //Login a particular automated service + //Stage 1 SSL Login Check: Generation of random string for this user + QString GenerateEncCheckString(); + //Stage 2 SSL Login Check: Verify that the returned/encrypted string can be decoded and matches the initial random string + QString LoginUC(QHostAddress host, QString encstring); + + private: QHash HASH; QHash IPFAIL; @@ -49,6 +54,9 @@ private: else{ return tmp.first(); } } + //SSL Decrypt function + QString DecryptSSLString(QString encstring, QString pubkey); + //PAM login/check files bool pam_checkPW(QString user, QString pass); void pam_logFailure(int ret); diff --git a/src/server/Dispatcher.cpp b/src/server/Dispatcher.cpp index a498f7e..98b1ae3 100644 --- a/src/server/Dispatcher.cpp +++ b/src/server/Dispatcher.cpp @@ -70,8 +70,12 @@ void DProcess::cmdFinished(int ret, QProcess::ExitStatus status){ proclog.append( this->readAllStandardOutput() ); //Now run any additional commands //qDebug() << "Proc Finished:" << ID << success << proclog; - if(success){ startProc(); }//will emit the finished signal as needed if no more commands - else{ + if(success && !cmds.isEmpty()){ startProc(); } + else if(success){ + t_finished = QDateTime::currentDateTime(); + emit ProcFinished(ID); + emit Finished(ID, ret, proclog); + }else{ if(status==QProcess::NormalExit){ proclog.append("\n[Command Failed: " + QString::number(ret)+" ]"); }else{ @@ -79,6 +83,7 @@ void DProcess::cmdFinished(int ret, QProcess::ExitStatus status){ } t_finished = QDateTime::currentDateTime(); emit ProcFinished(ID); + emit Finished(ID, ret, proclog); } } diff --git a/src/server/Dispatcher.h b/src/server/Dispatcher.h index 8ab12ea..78748e2 100644 --- a/src/server/Dispatcher.h +++ b/src/server/Dispatcher.h @@ -10,7 +10,6 @@ // == Simple Process class for running sequential commands == -// == INTERNAL ONLY - Do not use directly == class DProcess : public QProcess{ Q_OBJECT public: @@ -42,7 +41,8 @@ private slots: void cmdFinished(int, QProcess::ExitStatus); signals: - void ProcFinished(QString ID); + void ProcFinished(QString); //ID + void Finished(QString, int, QString); //ID, retcode, log }; diff --git a/src/server/WebBackend.cpp b/src/server/WebBackend.cpp index 1092fdd..79a76ea 100644 --- a/src/server/WebBackend.cpp +++ b/src/server/WebBackend.cpp @@ -95,7 +95,7 @@ RestOutputStruct::ExitCode WebSocket::EvaluateBackendRequest(const RestInputStru AvailableSubsystems(IN.fullaccess, &avail); if(!avail.contains(namesp+"/"+name)){ return RestOutputStruct::NOTFOUND; } } - + qDebug() << "Evaluate Backend Request:" << namesp << name; //Go through and forward this request to the appropriate sub-system if(namesp=="sysadm" && name=="settings"){ return EvaluateSysadmSettingsRequest(IN.args, out); @@ -125,24 +125,25 @@ RestOutputStruct::ExitCode WebSocket::EvaluateBackendRequest(const RestInputStru // === SYSADM SETTINGS === RestOutputStruct::ExitCode WebSocket::EvaluateSysadmSettingsRequest(const QJsonValue in_args, QJsonObject *out){ + qDebug() << "sysadm/settings Request:" << in_args; if(!in_args.isObject()){ return RestOutputStruct::BADREQUEST; } QJsonObject argsO = in_args.toObject(); QStringList keys = argsO.keys(); + qDebug() << " - keys:" << keys; if(!keys.contains("action")){ return RestOutputStruct::BADREQUEST; } QString act = argsO.value("action").toString(); bool ok = false; if(act=="register_ssl_cert" && keys.contains("pub_key")){ - //Additional arguments: "pub_key" (String), and the cert with that key must already be loaded into the connection - QString pub_key = argsO.value("pub_key").toString();\ - //Now find the currently-loaded certificate with the given public key - QList certs; - if(SOCKET!=0){ certs = SOCKET->sslConfiguration().peerCertificateChain(); } - else if(TSOCKET!=0){ certs = TSOCKET->peerCertificateChain(); } - for(int i=0; iRegisterCertificate(SockAuthToken, certs[i]); - } + //Required arguments: "pub_key" (String) + //Optional arguments: "nickname" (String), "email" (String) + QString pub_key, nickname, email; + pub_key = argsO.value("pub_key").toString(); + if(keys.contains("nickname")){ nickname = argsO.value("nickname").toString(); } + if(keys.contains("email")){ email = argsO.value("email").toString(); } + + if(!pub_key.isEmpty()){ + ok = AUTHSYSTEM->RegisterCertificate(SockAuthToken, pub_key, nickname, email); + if(!ok){ return RestOutputStruct::FORBIDDEN; } } }else if(act=="list_ssl_certs"){ AUTHSYSTEM->ListCertificates(SockAuthToken, out); diff --git a/src/server/WebSocket.cpp b/src/server/WebSocket.cpp index c58b79c..1900bea 100644 --- a/src/server/WebSocket.cpp +++ b/src/server/WebSocket.cpp @@ -145,7 +145,7 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ } //Now check the body of the message and do what it needs - if(out.in_struct.namesp.toLower() == "rpc"){ +if(out.in_struct.namesp.toLower() == "rpc"){ if(out.in_struct.name.startsWith("auth")){ //Now perform authentication based on type of auth given //Note: This sets/changes the current SockAuthToken @@ -157,15 +157,18 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ QString user, pass; if(out.in_struct.args.toObject().contains("username")){ user = JsonValueToString(out.in_struct.args.toObject().value("username")); } if(out.in_struct.args.toObject().contains("password")){ pass = JsonValueToString(out.in_struct.args.toObject().value("password")); } - if(!pass.isEmpty()){ + //Use the given password SockAuthToken = AUTHSYSTEM->LoginUP(host, user, pass); - }else{ - //No password - use the current SSL certificates instead - QList certs; - if(SOCKET!=0){ certs = SOCKET->sslConfiguration().peerCertificateChain(); } - else if(TSOCKET!=0){ certs = TSOCKET->peerCertificateChain(); } - SockAuthToken = AUTHSYSTEM->LoginUC(host, user, certs); + }else if(out.in_struct.name=="auth_ssl" && out.in_struct.args.isObject() ){ + if(!out.in_struct.args.toObject().contains("encrypted_string")){ + //Stage 1: Send the client a random string to encrypt with their SSL key + QString key = AUTHSYSTEM->GenerateEncCheckString(); + QJsonObject obj; obj.insert("test_string", key); + out.CODE = RestOutputStruct::PARTIALCONTENT; + }else{ + //Stage 2: Check the returned encrypted/string + SockAuthToken = AUTHSYSTEM->LoginUC(host, JsonValueToString(out.in_struct.args.toObject().value("encrypted_string")) ); } }else if(out.in_struct.name == "auth_token" && out.in_struct.args.isObject()){ SockAuthToken = JsonValueToString(out.in_struct.args.toObject().value("token")); @@ -193,17 +196,17 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ }else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Now provide access to the various subsystems // First get/set the permissions flag into the input structure - out.in_struct.fullaccess = AUTHSYSTEM->hasFullAccess(SockAuthToken); + out.in_struct.fullaccess = AUTHSYSTEM->hasFullAccess(SockAuthToken); //Pre-set any output fields - QJsonObject outargs; + QJsonObject outargs; out.CODE = EvaluateBackendRequest(out.in_struct, &outargs); - out.out_args = outargs; - }else{ + out.out_args = outargs; + }else{ //Bad/No authentication out.CODE = RestOutputStruct::UNAUTHORIZED; } - }else if(out.in_struct.namesp.toLower() == "events"){ +}else if(out.in_struct.namesp.toLower() == "events"){ if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Pre-set any output fields QJsonObject outargs; @@ -239,7 +242,7 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ out.CODE = RestOutputStruct::UNAUTHORIZED; } //Other namespace - check whether auth has already been established before continuing - }else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token +}else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Now provide access to the various subsystems // First get/set the permissions flag into the input structure out.in_struct.fullaccess = AUTHSYSTEM->hasFullAccess(SockAuthToken); @@ -247,10 +250,11 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ QJsonObject outargs; out.CODE = EvaluateBackendRequest(out.in_struct, &outargs); out.out_args = outargs; - }else{ +}else{ //Error in inputs - assemble the return error message out.CODE = RestOutputStruct::UNAUTHORIZED; - } +} + //If this is a REST input - go ahead and format the output header if(out.CODE == RestOutputStruct::OK){ out.Header << "Content-Type: text/json; charset=utf-8"; diff --git a/src/server/WebSocket.h b/src/server/WebSocket.h index b30d837..1eae6a6 100644 --- a/src/server/WebSocket.h +++ b/src/server/WebSocket.h @@ -27,7 +27,6 @@ private: QString SockID, SockAuthToken, SockPeerIP; AuthorizationManager *AUTHSYSTEM; QList ForwardEvents; - void sendReply(QString msg); //Main connection comminucations procedure diff --git a/src/server/server.pro b/src/server/server.pro index e99e9db..c9c768c 100644 --- a/src/server/server.pro +++ b/src/server/server.pro @@ -37,4 +37,4 @@ INSTALLS += target QMAKE_LIBDIR = /usr/local/lib/qt5 /usr/local/lib INCLUDEPATH += /usr/local/include -LIBS += -L/usr/local/lib -lpam -lutil +LIBS += -L/usr/local/lib -lpam -lutil -lssl -lcrypto