Search code examples
c++qtauthenticationsslnetwork-programming

It is okay to call QDialog::exec from QsslServer::sslErrors?


I am Developing an application that uses SSL Sockets to communicate, When an untrusted client connects to the server it emits the sslError Now I am supposed to ask the user for decision with QDialog::exec()(I can't go for async operations since I need to process the error inside sslError either ignoreSslError or abort connection depends upon user decision) But when i open the dialog The client is disconnected (client socket seems to be deleted) then QDialog exec returned when I accessed the socket it throws an error as memory access violation.

Qt docs for QDialog::exec says

Note: Avoid using this function; instead, use open(). Unlike exec(), open() is asynchronous, and does not spin an additional event loop. This prevents a series of dangerous bugs from happening (e.g. deleting the dialog's parent while the dialog is open via exec()). When using open() you can connect to the finished() signal of QDialog to be notified when the dialog is closed.

What should I do in this situation?

I have an alternative process of authentication that starts with the connection if there are any sslHandshakeErrors then later I will show dialog asynchronously if user press ok I send a packet named authOk and proceed to read else I send a packet named authFailed and disconnect the client. But in this process, it seems like not using a standard SSL connection handshake.

Are any better Solutions, Thanks :)

Edit:

Example:


#include <QApplication>
#include <QSslServer>
#include <QDialog>
#include <QSslKey>
#include <QMessageBox>

int main(int argc, char **argv) {
  // create an Application
  QApplication app(argc, argv);

  // set the ssl configuration
  auto config = QSslConfiguration();

  // set certificate
  config.setLocalCertificate(QSslCertificate::fromData(QByteArray::fromStdString(R"(
-----BEGIN CERTIFICATE-----
MIIDITCCAgmgAwIBAgIVAMdt4c6oGd0rUSbR+/tBVfhny3K3MA0GCSqGSIb3DQEB
BQUAMEoxGDAWBgNVBAMMD0xBUFRPUC1KQzJNMzcyQTEbMBkGA1UECgwSc3JpbGFr
c2htaWthbnRoYW5wMREwDwYDVQQLDAhjbGlwYmlyZDAeFw0yMzA5MDgxMTQ4NDZa
Fw0yNDA5MDcxMTQ4NDZaMEoxGDAWBgNVBAMMD0xBUFRPUC1KQzJNMzcyQTEbMBkG
A1UECgwSc3JpbGFrc2htaWthbnRoYW5wMREwDwYDVQQLDAhjbGlwYmlyZDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALkFDUKmzz+NE0x7o5JGgE8KZ/cB
GEtTwN2cElMESQ/MZe3ohIE5GdFkld/7894wXZAJkXu38oZVEQ/AurntqqSHT4iP
7qMUJGYjo2v2g/1JwU3E/sG+IjatOjyl8b0b+E9TmDt7XOS4VWz3vjTkXwmQTzUv
3L5myfdcmBpA9AOXxH8yHq9lcBl97ZHGWA4zI9uWnwtyFWSl8DX4H/y14+itEYwH
n6xLsEBxqjx40G7WR0AiAYRdE5Yvr6QOGtFAeODFqOv/2sAxdm/7P1wHjIvyOB++
eufvPyWZpzeBfEvrIeGkj91YGAV7FQsOfCuzetGgZcLiBDAZKjnKEQ2oajUCAwEA
ATANBgkqhkiG9w0BAQUFAAOCAQEAD/SYccAb3K6GKyfc9Rbaj44IxpsNlHDQAr5b
c8Nmz+LW905EqFe6tAhCgi3q9o3HUUeiNHe0rYad3Lgd1setSOVdWiSbxArmELgW
Dg3NGd3GIIRShvmZfSHRkpvKaD9j06CVzMrM0nZsjQVcQrKlFBUJ9UEqVmmcz1nU
a4yUEQ9Rb7t4Icw7aD07NqLRlhNGCii4d12NAY7kRZdLdtTw7T/j4tXxmcJsOiWK
Lx/cqqTBgBHc3l3EeylQdO17pFClY5yMUGGla7LPcDm1sU4mmFEKmkjcRGu+mQM2
V3EDgh37GyTWvs6Zf86B14m/US0Ff4vQu26vco5Pjk3xKckA+Q==
-----END CERTIFICATE-----
)")).first());

  // set private key
  config.setPrivateKey(QSslKey(QByteArray::fromStdString(R"(
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAuQUNQqbPP40TTHujkkaATwpn9wEYS1PA3ZwSUwRJD8xl7eiE
gTkZ0WSV3/vz3jBdkAmRe7fyhlURD8C6ue2qpIdPiI/uoxQkZiOja/aD/UnBTcT+
wb4iNq06PKXxvRv4T1OYO3tc5LhVbPe+NORfCZBPNS/cvmbJ91yYGkD0A5fEfzIe
r2VwGX3tkcZYDjMj25afC3IVZKXwNfgf/LXj6K0RjAefrEuwQHGqPHjQbtZHQCIB
hF0Tli+vpA4a0UB44MWo6//awDF2b/s/XAeMi/I4H7565+8/JZmnN4F8S+sh4aSP
3VgYBXsVCw58K7N60aBlwuIEMBkqOcoRDahqNQIDAQABAoIBADf6I0zrEwDzOceG
ELMMyX0gdNvyZNtFd7CUq6aaQVCaUqxsEMrX78u+VunxXJL4pxYRDwcTXDjwO708
XkIqedpVZea3RUfprCmK1sKvTrevPOC+nSUY1Vkdh+UZf83rTHETpZc5d51rd80E
F3QBNA+8rXo2BN9GUgyY4xvuUuVS3drVZ6WA9A9yPtaBgeLATqmb0Ckh2aVn+XG9
eYIxiF0Sfnb3HET7IDO+Xtw8OIygq+dT9v5LMMYf/Aa8aakJMoTK6SLbjFWs5gUj
mhBSBdxqdmLLTa2E+3hrZOfgQ0tk85336n7v3dFKkhTMr9KztOIh53Ruh36gR3UT
QxbCyvECgYEA2uKcFmrkGa61WarqSbB58e/m111cmlcjOSoEELwb+O1pLe8qqDSZ
UeLVMRW+omdyvt0N1RaDEOsBWtgup2RntutEx8sAi5O1d+CrCLZ7xZJPSrYH4tIs
YFXoBgCNqn+275ZdeL5LgxuFKAstFW9YbWpsXCEPbrIMBk4Xm3m3Pt8CgYEA2GRo
I+lLPvcslTwens+5sXT5+EPTcY9Ss2jSz3njEIt4asxa/P4H7Xx2EWqMQg5LGSVD
MP8L9lxCSiyYvrQhYAQZq2VqvVCejFkrWY8hntDvyhJ26SfanSdMB1MVWOc16dns
wjuuX3+5QIQoMogL1eng6/VOOJHVfiAh57yqPWsCgYEAkcI54wvHXfrjtRSF9BBb
BGuXM29ujTDdueFq16IMlpWyZu5PX7e3Kbp98bPjQM7WsJcP8QiOuyNjwZUYbEwG
bN767HkYodn5DB1GiATNI2Is/zl8wuTmvDg4zFZuAE4QCjf9grxmGKao42Od4BpH
roUiJ6+0USirrT8vpU9GYc0CgYAYuIfJKnrFK7m1JtQcsoB1THbOLPl37La29k+3
EialmjlcghIW+vJu6BwY60Iwva9IpSAi9dApCesszCF7D9sMPAuur/xculwSjpFM
PvTJTvdF74wUINBxya5+27gBmxBmsdXBbs4B7PZ971skQrSPcJOYgUK5ZbetHACj
l8MfFwKBgQC3g39rKU/iahrr5VcbLecsdv9jhSo+PANQOXhreEZTOF1spsWh9bMN
11MB0uZu99p4Solvv0M11Md++CO3ocBRT0AsdsdLdytAb+YWM+c1ls5dhjVnaMpL
dKKHiQ7sfiPM05f6HuTmMuinSLw7f1Ff7GVJUMugJCqiNn3XO3jWGQ==
-----END RSA PRIVATE KEY-----
)"), QSsl::KeyAlgorithm::Rsa));

  // peer verify
  config.setPeerVerifyMode(QSslSocket::VerifyPeer);

  // if any of them is null
  if (config.isNull() || config.localCertificate().isNull() || config.privateKey().isNull()) {
    throw std::runtime_error("Can't Create QSslConfiguration");
  }

  const auto processSslErrors = [=] (QSslSocket * sock, const QList<QSslError> & errors){
    // create a dialog
    auto dialog = QMessageBox(QMessageBox::Critical, "SSL Error", "Accept ?", QMessageBox::Yes | QMessageBox::Cancel);

    // show the dialog
    dialog.exec();

    // if user accepted
    if (dialog.result() == QMessageBox::Yes) {
      sock->ignoreSslErrors();
    }
  };

  // create the server
  auto server = new QSslServer();

  // set the ssl errors handler
  QObject::connect(server, &QSslServer::sslErrors, processSslErrors);

  // set the ssl configuration
  server->setSslConfiguration(config);

  // listen to the port
  server->listen(QHostAddress::Any, 7000);

  // exec
  return app.exec();
}

Exception:

Exception

In Client Side:

openssl s_client -connect 127.0.0.1:7000

You can see the client disconnected while the dialog is in open


Solution

  • A better solution may be to accept the ssl errors asynchronously:

    • Reject the connection if the error was not yet approved by the user
    • Asynchronously ask the user if he wants to ignore such an error. Use this user decision when the same ssl error occurs again.

    Even if Qt would support the synchronous approach, asking for user consent synchronously will often result in timed out connections as the user may not respond fast enough.

    A basic code example (in a real situation you should check the reported errors):

    bool acceptErrors = false; // some global state to keep track of the errors the user allowed to ignore.
    
    const auto processSslErrors = [=] (QSslSocket * sock, const QList<QSslError> & errors){    
      if (acceptErrors) // check if the user wants to ignore this error
        sock->ignoreSslErrors();
      else {
        // reject the connection for now, but ask the user if he wants to ignore this error in the future
        QTimer::singleShot(0, [](){
          auto dialog = QMessageBox(QMessageBox::Critical, "SSL Error", "Accept ?", QMessageBox::Yes | QMessageBox::Cancel);
      
          // show the dialog
          dialog.exec();
      
          // if user accepted
          if (dialog.result() == QMessageBox::Yes) {
            acceptErrors = true; 
          }
        });
      }
    };