QSslSocket双向认证设置

1 证书生成

因为目标是实现双向认证,所以需要将自己的公钥和私钥以及对端的私钥加载到Qt的安全环境中。证书可借助keytoolopenssl工具生成,总结几个比较常用的生成命令如下:

# 生成jks格式密钥库
keytool -genkey -v -alias tomcat -keyalg RSA -keystore tomcat.keystore -validity 36500
# 从jks格式密钥库中导出证书(DER格式)
keytool -keystore  tomcat.keystore -export -alias tomcat -file server.cer  -storepass 123456
# 生成p12格式密钥库
keytool -genkey -v -alias mykey -keyalg RSA -storetype PKCS12 -keystore mykey.p12 -storepass 123456
# 将jks格式密钥库转化为p12格式
keytool -importkeystore -srckeystore tomcat.keystore -destkeystore tomcat.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass 123456 -deststorepass 123456 -srcalias tomcat -destalias tomcat -srckeypass 123456 -destkeypass 123456 -noprompt
# 从p12密钥库中导出公钥和私钥(PEM格式)
openssl pkcs12 -clcerts -nokeys -in mykey.p12 -out cert.pem
openssl pkcs12 -nocerts -nodes -in mykey.p12 -out private.pem
# 向jks格式密钥库中导入可信任的证书
keytool -import -v -file cert.pem -keystore clients.keystore  -storepass 123456

2 QSslSocket设置

首先客户端和服务器都必须加载本地的私钥、证书和信任库。在QSslSocket中这三个设置分别对应localCertificate, privateKey和caCertificates。同时双向认证需要设置VerifyPeer和Depth = 1。以客户端为例,加载方法如下:

bool ClientSimulator::loadSslFiles()
{
    bool openOk = false;
    QFile certFile(QDir::currentPath() + QString("/sslCert/server.cer"));
    openOk = certFile.open(QIODevice::ReadOnly);
    m_certificate = QSslCertificate(certFile.readAll(), QSsl::Der);
    openOk &= !m_certificate.isNull();
 
    QFile keyFile(QDir::currentPath() + QString("/sslCert/ckey.pem"));
    openOk &= keyFile.open(QIODevice::ReadOnly);
    m_privateKey = QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
    openOk &= !m_privateKey.isNull();
 
    QFile peerFile(QDir::currentPath() + QString("/sslCert/cert.pem"));
    openOk &= peerFile.open(QIODevice::ReadOnly);
    QSslCertificate peerCert(peerFile.readAll(), QSsl::Pem);
    bool peerCertValid = !peerCert.isNull();
    openOk &= peerCertValid;
 
    QList caCerts;
    caCerts << peerCert;
    m_caCertificates = caCerts;
 
    return openOk;
}

之后在客户端连接到服务器时,设置加载好的证书和密钥:

if(loadSslFiles())
{
    m_socket->setLocalCertificate(m_certificate);
    m_socket->setPrivateKey(m_privateKey);
    m_socket->setCaCertificates(m_caCertificates);
    m_socket->setPeerVerifyMode(QSslSocket::VerifyPeer);
    m_socket->setPeerVerifyDepth(1);
    m_socket->connectToHostEncrypted(ui->lineEditIP->text(), port);
}
else
{
    QMessageBox::warning(this, "SSL File Error", "Load SSL Files failed.");
}

这里m_socket是QSslSocket类的实例。
服务器端的设置类似,重载incommingConnection方法,参考实现如下:

void SslServer::incomingConnection(qintptr socketDescriptor)
{
    if(!m_client.isNull())
    {
        m_client->disconnectFromHost();
        disconnect(m_client, SIGNAL(readyRead()), this, SLOT(onRecvFromClient()));
        disconnect(m_client, SIGNAL(sslErrors(QList)), this, SLOT(onSslErrors(QList)));
        delete m_client;
    }
    m_client = new QSslSocket(this);
    m_client->setSocketDescriptor(socketDescriptor);
    if(m_sslConfig != NULL)
    {
        m_client->setLocalCertificate(m_sslConfig->certificate());
        m_client->setPrivateKey(m_sslConfig->privateKey());
        m_client->setCaCertificates(m_sslConfig->caCertificates());
    }
    m_client->setPeerVerifyMode(QSslSocket::VerifyPeer);
    m_client->setPeerVerifyDepth(1);
    connect(m_client, SIGNAL(readyRead()), this, SLOT(onRecvFromClient()));
    connect(m_client, SIGNAL(sslErrors(const QList &)), this, SLOT(onSslErrors(const QList &)));
    m_client->startServerEncryption();
    QTcpServer::incomingConnection(socketDescriptor);
}

注意:

  • 在客户端连接服务端时,要调用加密连接方法connectToHostEncrypted()。如果使用普通的链接connectToHost()方法,会报无效套接字的错。
  • 同理,在服务端连接客户端时,需调用加密通信方法startServerEncryption()。
  • 连接的IP要和信任库中证书所提供的IP一致,否则可能会出现IP不匹配的告警。
  • 如果ssl环境设置需要在多个地方复用,可以将设置统一加载到QSslConfiguration类的实例中,之后通过QSslSocket的setSslConfiguration方法进行加载。QSslConfiguration提供的接口与上面范例中的比较类似,这里就不赘述了。
  • 除上述直接载入证书和秘钥文件的方法外,qt5.4之后还支持直接从pkcs12格式的文件解析并载入证书,调用静态方法QSslCertificate::importPkcs12()即可,范例如下:
QFile keyFile("/certs/ks.p12");
bool openOK = keyFile.open(QIODevice::ReadWrite);

QSslKey key;
QSslCertificate certs;
QList caCerts;

QByteArray passPhrase = QString("test123").toLatin1();
openOK = QSslCertificate::importPkcs12(&keyFile, &key, &certs, &caCerts, passPhrase);
keyFile.close();

你可能感兴趣的:(QSslSocket双向认证设置)