Qt网络编程:QSslSocket

一、描述

QSslSocket 建立安全、加密的 TCP 连接,您可以使用它来传输加密数据。它可以在客户端和服务器模式下运行,支持 SSL 协议,包括 SSL 3 和 TLS 1.2。 默认情况下,QSslSocket 仅使用被认为是安全的 SSL 协议 (QSsl::SecureProtocols),但您可以通过调用 setProtocol() 来更改 SSL 协议,只要您在握手开始之前执行此操作即可

在Socket进入 ConnectedState 后,SSL 加密在现有 TCP 流之上运行。有两种使用 QSslSocket 建立安全连接的简单方法:使用立即 SSL 握手,或在未加密模式下建立连接后延迟 SSL 握手。

使用 QSslSocket 最常见的方法是构造一个对象并通过调用 connectToHostEncrypted() 启动安全连接。一旦建立连接,此方法立即开始 SSL 握手。

  QSslSocket *socket = new QSslSocket(this);
  connect(socket, SIGNAL(encrypted()), this, SLOT(ready()));
  socket->connectToHostEncrypted("imap.example.com", 993);

与普通的 QTcpSocket 一样,如果连接成功,QSslSocket 会进入 HostLookupStateConnectingState 和最后的 ConnectedState状态。然后握手自动开始,如果成功,则发出 encrypted() 信号以指示Socket已进入加密状态并准备好使用。

请注意,数据可以在从 connectToHostEncrypted() 返回后立即写入Socket(即,在发出 encrypted() 信号之前)。数据在 QSslSocket 中排队,直到发出 encrypted() 信号再进行处理。

使用延迟 SSL 握手保护现有连接的示例是 SSL 服务器保护传入连接的情况。假设创建一个QTcpServer 的子类作为 SSL 服务器。可以进行:

  void SslServer::incomingConnection(qintptr socketDescriptor)
  {
      QSslSocket *serverSocket = new QSslSocket;
      if (serverSocket->setSocketDescriptor(socketDescriptor)) 
      {
          addPendingConnection(serverSocket);
          connect(serverSocket, &QSslSocket::encrypted, this, &SslServer::ready);
          serverSocket->startServerEncryption();
      } 
      else 
      {
          delete serverSocket;
      }
  }

当有客户端连接服务端时会调用QTcpServer::incomingConnection(),重写此函数可以实现自己的服务器操作。这里首先构造 QSslSocket 的实例,然后调用 setSocketDescriptor() 将新Socket的描述符设置为传入的现有Socket。然后通过调用启动 SSL 握手启动服务器加密()。

如果发生错误,QSslSocket 会发出 sslErrors() 信号。在这种情况下,如果不采取任何措施来忽略错误,则连接将被丢弃。发生错误要继续操作时,可以调用 ignoreSslErrors(),这将允许 QSslSocket 在建立对等方的身份时忽略它遇到的错误。在 SSL 握手期间忽略错误应该谨慎使用,因为安全连接的一个基本特征是它们应该通过成功的握手来建立。

加密后,可以将 QSslSocket 当做常规 QTcpSocket使用。

QSslSocket 支持 QTcpSocket 的阻塞函数 waitForConnected()、waitForReadyRead()、waitForBytesWritten() 、 waitForDisconnected()。它还提供了waitForEncrypted(),此函数将阻塞调用线程,直到建立了加密连接。

bytesWritten() 信号和 encryptedBytesWritten() 信号之间的区别:

  • 对于 QTcpSocket,bytesWritten() 将在数据写入 TCP Socket后立即发出。
  • 对于 QSslSocket,bytesWritten() 将在数据被加密时发出,而 encryptedBytesWritten() 将在数据写入 TCP Socket后立即发出。

二、类型成员

1、QSslSocket::PeerVerifyMode:描述 QSslSocket 的对等验证模式。

  • VerifyNone:QSslSocket 不会向对等方请求证书。如果对连接另一端的身份不感兴趣,可以设置此模式。连接仍将被加密,并且如果对等方请求,本地Socket仍将其本地证书发送给对等方。
  • QueryPeer:QSslSocket 将从对等方请求证书,但不要求此证书有效。当希望向用户显示对等证书详细信息而不影响实际 SSL 握手时,这很有用。此模式是服务器的默认模式。
  • VerifyPeer:QSslSocket 会在 SSL 握手阶段向对端请求证书,并要求该证书有效。失败时,QSslSocket 将发出 QSslSocket::sslErrors() 信号。此模式是客户端的默认模式。
  • AutoVerifyPeer:默认设置,QSslSocket 将自动为服务器Socket使用 QueryPeer,为客户端Socket使用 VerifyPeer。

2、QSslSocket::SslMode:描述 QSslSocket 可用的连接模式。

  • UnencryptedMode:Socket未加密。 它的行为与 QTcpSocket 相同。
  • SslClientMode:Socket是客户端 SSL Socket。
  • SslServerMode:Socket是服务器端 SSL Socket。

三、成员函数

1、[signal] void encrypted()

当 QSslSocket 进入加密模式时会发出此信号。发出此信号后, isEncrypted() 将返回 true,并且Socket上的所有进一步传输都将被加密。

2、[signal] void encryptedBytesWritten(qint64 written)

当 QSslSocket 将其加密数据写入网络时,会发出此信号。参数为成功写入的字节数。

3、void ignoreSslErrors()

指示QSslSocket 握手阶段忽略错误并继续连接。如果想在握手阶段发生错误的情况下继续连接,那么您必须从连接到 sslErrors() 信号的槽函数或在握手阶段之前调用此函数。如果不调用此插槽,无论是响应错误还是在握手之前,都将在发出 sslErrors() 信号后断开连接。

4、[signal] void modeChanged(QSslSocket::SslMode mode)

当模式从 QSslSocket::UnencryptedMode 更改为 QSslSocket::SslClientMode 或 QSslSocket::SslServerMode 时,会发出此信号。参数是新模式。

5、[signal] void newSessionTicketReceived()

如果在握手期间协商了 TLS 1.3 协议,则 QSslSocket 在收到 NewSessionTicket 消息(服务器收到客户端Finished消息后,它都可以发送NewSessionTicket消息)后发出此信号。

此功能仅在 OpenSSL 后端启用,并且需要 OpenSSL v 1.1.1 或更高版本。

6、[signal] void peerVerifyError(const QSslError &error)

在建立加密之前,QSslSocket 可以在 SSL 握手期间多次发出此信号,以指示在建立对等方身份时发生了错误。该错误通常表示 QSslSocket 无法安全地识别对等方。通过连接到这个信号,可以在握手完成之前手动选择从连接的槽函数内部拆除连接。如果没有采取任何行动,QSslSocket 将继续发出 QSslSocket::sslErrors()。

7、[signal] void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator)

QSslSocket 在协商 PSK 密码套件时发出此信号,因此需要 PSK 身份验证。

使用 PSK 时,客户端必须向服务器发送一个有效的身份和一个有效的预共享密钥,以便 SSL 握手继续进行。 应用程序可以在连接到这个信号的槽中提供这个信息,根据他们的需要填充传递的验证器对象。

注意:忽略此信号,或未能提供所需的凭据,将导致握手失败,从而中止连接。

注意:authenticator 对象归Socket所有,不得被应用程序删除。

8、[signal] void sslErrors(const QList &errors)

QSslSocket 在 SSL 握手后发出此信号以指示在建立对等方身份时发生了错误。这些错误通常表明 QSslSocket 无法安全地识别对等方。除非采取行动,否则在发出此信号后将断开连接。参数是发生的错误列表。

如果想在发生错误的情况下继续连接,您必须从连接到此信号的槽函数内部调用 ignoreSslErrors()。如果以后需要访问错误列表,可以调用 sslHandshakeErrors()。

注意:连接到该信号时不能使用 Qt::QueuedConnection,否则调用 ignoreSslErrors() 将不起作用。

9、void startClientEncryption()

为客户端连接启动延迟 SSL 握手。当Socket处于 ConnectedState 但仍处于 UnencryptedMode 时,可以调用此函数。 如果尚未连接,或者已经加密,则此功能无效。

10、void startServerEncryption()

为服务器连接启动延迟 SSL 握手。 当Socket处于 ConnectedState 但仍处于 UnencryptedMode 时,可以调用此函数。 如果未连接或已加密,则该功能无效。

对于服务器Socket,调用此函数是发起 SSL 握手的唯一方法。大多数服务器会在收到连接后立即调用此函数,或者由于收到了进入 SSL 模式的特定于协议的命令。

实现 SSL 服务器的最常见方法是创建 QTcpServer 的子类并重新实现 QTcpServer::incomingConnection()。 然后将返回的Socket描述符传递给setSocketDescriptor()。

11、void abort()

中止当前连接并重置Socket。与 disconnectFromHost() 不同,该函数会立即关闭Socket,清除写入缓冲区中的所有挂起数据。

12、qint64 bytesAvailable()

返回可立即读取的解密字节数。

13、qint64 bytesToWrite()

返回等待加密并写入网络的未加密字节数。

14、void connectToHostEncrypted(const QString &hostName, quint16 port, QIODevice::OpenMode mode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)

在端口上启动到设备主机名的加密连接。这相当于调用 connectToHost() 建立连接,然后调用 startClientEncryption()。 protocol 参数可用于指定要使用的网络协议(例如 IPv4 或 IPv6)。

QSslSocket 首先进入HostLookupState。然后,在进入事件循环或 waitFor...() 函数之一后,它进入 ConnectingState,发出 connected(),然后启动 SSL 客户端握手。在每次状态更改时,QSslSocket 都会发出信号 stateChanged()。

发起 SSL 客户端握手后,如果无法建立对等方的身份,则发出信号 sslErrors()。如果要忽略错误并继续连接,则必须从连接到 sslErrors() 信号的槽函数内部或在进入加密模式之前调用 ignoreSslErrors()。如果ignoreSslErrors()没有被调用,连接被丢弃,将发出disconnected()信号,并进入UnconnectedState状态。

如果 SSL 握手成功,QSslSocket 会发出 encrypted()信号。

15、void connectToHostEncrypted(const QString &hostName, quint16 port, const QString &sslPeerName, QIODevice::OpenMode mode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)

重载函数。使用主机名 (sslPeerName) 进行证书验证。

16、qint64 encryptedBytesAvailable()

返回等待解密的加密字节数。 通常此函数返回0,因为QSslSocket会尽快解密其传入数据。

17、qint64 encryptedBytesToWrite() 

返回等待写入网络的加密字节数。

18、bool flush()

此函数尽可能多地从内部写入缓冲区写入底层网络Socket。如果写入了任何数据,此函数返回true。

如果需要QSslSocket立即开始发送缓冲数据,请调用此函数。成功写入的字节数取决于操作系统。在大多数情况下,您不需要调用此函数,因为一旦控件返回到事件循环,QAbstractSocket将自动开始发送数据。如果没有事件循环,则应该调用waitForBytesWritten()。

19、void ignoreSslErrors(const QList &errors)

此方法告诉 QSslSocket 将忽略列表中给出的错误。

注意:由于大多数 SSL 错误都与证书相关联,因此对于其中的大多数错误,必须设置与此 SSL 错误相关的预期证书。 例如,如果想连接到使用自签名证书的服务器,可以考虑:

 QList<QSslCertificate> cert = QSslCertificate::fromPath(QLatin1String("server-certificate.pem"));
 QSslError error(QSslError::SelfSignedCertificate, cert.at(0));
 QList<QSslError> expectedSslErrors;
 expectedSslErrors.append(error);

 QSslSocket socket;
 socket.ignoreSslErrors(expectedSslErrors);
 socket.connectToHostEncrypted("server.tld", 443);

20、bool isEncrypted()

如果Socket已加密,则返回 true。

可以调用 sessionCipher() 来查找用于加密和解密数据的加密密码。

21、QSslCertificate localCertificate()

返回Socket的本地证书。

22、QListlocalCertificateChain()

返回Socket的本地证书链。

23、QSslCertificate peerCertificate()

返回对等方的数字证书(即所连接主机的证书)。

在握手阶段会自动检查对等证书,因此此功能通常用于获取证书以进行显示或用于连接诊断目的。它包含有关对等方的信息,包括其主机名、证书颁发者和对等方的公钥。

24、QList peerCertificateChain()

返回对等方的数字证书链,或空的证书列表。

25、QString peerVerifyName()

返回用于证书验证的不同主机名,由 setPeerVerifyName() 或 connectToHostEncrypted() 设置。

26、QSslKey privateKey()

获取socket的私钥。

27、QSsl::SslProtocol protocol()

返回Socket的 SSL 协议。 默认为 QSsl::SecureProtocols

28、void resume()

暂停后继续在Socket上传输数据。

29、QSslCipher sessionCipher()

返回Socket的加密密码,如果连接未加密,则返回空密码。 会话的Socket密码在握手阶段设置。 密码用于加密和解密通过Socket传输的数据。

30、QSsl::SslProtocol sessionProtocol()

如果连接未加密,则返回Socket的 SSL/TLS 协议或 UnknownProtocol。会话的Socket协议在握手阶段设置。

31、void setLocalCertificate(const QSslCertificate &certificate)

设置Socket的本地证书。如果需要向对等方确认自己的身份,则需要本地证书。与私钥一起使用。

本地证书和私钥对于服务器Socket始终是必需的。

32、void setPeerVerifyDepth(int depth)

将在 SSL 握手阶段要检查的对等方证书链中的最大证书数设置为深度。 设置深度为 0 表示没有设置最大深度,表示应该检查整个证书链。

33、void setPeerVerifyName(const QString &hostName)

为证书验证设置一个不同的主机名,由 hostName 给出,而不是用于 TCP 连接的主机名。

34、void setPrivateKey(const QSslKey &key)

将Socket的私钥设置为 key。私钥和本地证书由必须向 SSL 对等方证明其身份的客户端和服务器使用。

需要私钥和本地证书的情况:

  • 创建 SSL 服务器Socket:始终需要。
  • 创建 SSL 客户端Socket:如果客户端必须向 SSL 服务器标识自己,则需要密钥和本地证书。

35、void setReadBufferSize(qint64 size)

设置 QSslSocket 的内部读取缓冲区的大小(字节)。

36、bool setSocketDescriptor(qintptr socketDescriptor, QAbstractSocket::SocketState state = ConnectedState, QIODevice::OpenMode openMode = ReadWrite)

使用本机Socket描述符 socketDescriptor 初始化 QSslSocket。 如果 socketDescriptor 被接受为有效的Socket描述符,则返回 true。socket以openMode指定的模式打开,进入state指定的socket状态。

37、void setSslConfiguration(const QSslConfiguration &configuration)

设置Socket的 SSL 配置对象。此函数将本地证书、密码、私钥和 CA 证书设置为存储在配置中的那些。

38、QList sslHandshakeErrors()

返回最后发生的 SSL 错误的列表。 这与 QSslSocket 通过 sslErrors() 信号传递的列表相同。如果连接已加密且没有错误,则此函数将返回一个空列表。

39、[static] long sslLibraryBuildVersionNumber()

返回编译时使用的 SSL 库的版本号。如果没有可用的 SSL 支持,那么这将返回一个未定义的值。

40、[static] QString sslLibraryBuildVersionString()

返回编译时使用的 SSL 库的版本字符串。如果没有可用的 SSL 支持,那么这将返回一个空值。

41、[static] long sslLibraryVersionNumber()

返回正在使用的 SSL 库的版本号。这是运行时使用的库版本,而不是编译时。如果没有可用的 SSL 支持,那么这将返回一个未定义的值。

42、[static] QString sslLibraryVersionString()

返回正在使用的 SSL 库的版本字符串。这是运行时使用的库版本,而不是编译时。

43、[static] bool supportsSsl()

如果此平台支持 SSL,则返回 true。如果平台不支持 SSL,则Socket将在连接阶段失败。

44、bool waitForConnected(int msecs = 30000)

等待Socket连接 msecs 毫秒(成功连接就返回)。如果连接建立返回true。

45、bool waitForDisconnected(int msecs = 30000)

等待Socket断开连接 msecs 毫秒(成功断开就返回)。如果连接已断开返回true。

46、bool waitForEncrypted(int msecs = 30000)

等待Socket完成 SSL 握手并发出 encrypted() 或 msecs 毫秒,以先到者为准。 如果已发出 encrypted(),则此函数返回 true; 否则(例如,Socket断开连接,或 SSL 握手失败),返回 false。如果 msecs 为 -1,则此函数不会超时。

以下示例最多等待一秒钟以加密Socket:

 socket->connectToHostEncrypted("imap", 993);
 if (socket->waitForEncrypted(1000))
     qDebug("Encrypted!");
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值