可以从QsslServer::sslErrors调用QDialog::exec吗?

ih99xse1  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(95)

我正在开发一个使用SSL套接字进行通信的应用程序,当一个不受信任的客户端连接到服务器时,它会发出sslError现在我应该要求用户使用QDialog::exec()做出决定(我不能进行CNOOC操作,因为我需要处理sslError内部的错误,无论是删除SslError还是中止连接都取决于用户的决定)但是当我打开对话框时,客户端断开连接(客户端套接字似乎被删除),然后QDialog exec返回当我访问套接字时,它会抛出一个错误,因为内存访问违规。
Qt docs for QDialog::exec说:
注意:避免使用此功能;相反,使用open()。与exec()不同,open()是异步的,并且不会旋转额外的事件循环。这可以防止一系列危险的错误发生(例如,在对话框打开时通过exec()删除对话框的父对话框)。当使用open()时,您可以连接到QDialog的finished()信号,以便在对话框关闭时得到通知。
在这种情况下我该怎么办?
我有一个身份验证的替代过程,如果有任何sslHandshakeErrors,那么稍后我将异步显示对话框,如果用户按ok,我发送一个名为authOk的数据包,然后继续读取,否则我发送一个名为authFailed的数据包,并断开客户端。但在这个过程中,似乎没有使用标准的SSL连接握手。
有更好的解决方案,谢谢:)

编辑:

范例:

#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();
}

例外情况:

在客户端:

openssl s_client -connect 127.0.0.1:7000

当对话框处于打开状态时,您可以看到客户端已断开连接

0pizxfdo

0pizxfdo1#

一个更好的解决方案可能是异步接受ssl错误:

  • 如果错误尚未被用户批准,则断开连接
  • 异步询问用户是否要忽略这样的错误。当同样的ssl错误再次发生时使用这个用户决定。

即使Qt支持同步方法,同步请求用户同意也经常会导致连接超时,因为用户可能响应不够快。
一个基本的代码示例(在真实的情况下,您应该检查报告的错误):

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; 
      }
    });
  }
};
eyh26e7m

eyh26e7m2#

这是建筑学的问题。

  • 每个线程都可以有一个事件循环,但小部件只能存在于主线程中。
  • QDialog::exec创建一个模式对话框,它会停止主线程内对话框之外的大多数事件处理,但不会停止其他线程。
  • 你需要一个事件循环来处理信号。
  • 您需要运行事件循环来异步处理数据。

你需要有一个或两个额外的线程,所以你可以有单独的偶数循环:

  1. Main -用户界面线程(UI)
    1.管理器线程(MT)-状态机,跟踪应用程序的状态(可选)
    1.通信线程(CT)-会话或套接字所在的位置
    MT的状态机可能在Communication中实现,但请确保在需要的地方使用queued connection
    这样,你就可以完全消除每一边可能的各种变化状态。用户的动作将被发送给MT,MT将决定如何处理CT状态并将当前的开发通知UI。CT处理数据,当结果准备好时,它向MT或UI发出信号。
    作为一个附带的好处,你会有一个结构匹配单一责任原则和可能的开放封闭原则。你只会改变UI来改变它对用户操作或状态变化的React,即。“输入”。你改变CT只是为了改变方案。
    请注意,您只能将没有父对象的对象移动到另一个线程,否则moveToThread将默默失败。为了简化UI方面的事情,你可能需要某种构建器模式,一个你移动到线程的对象,它将创建其余的东西。

相关问题