美文网首页Qt学习程序员
QSslSocket双向认证设置

QSslSocket双向认证设置

作者: ArcDriver | 来源:发表于2018-02-17 11:17 被阅读144次

    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<QSslCertificate> 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<QSslError>)), this, SLOT(onSslErrors(QList<QSslError>)));
            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<QSslError> &)), this, SLOT(onSslErrors(const QList<QSslError> &)));
        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<QSslCertificate> caCerts;
    
    QByteArray passPhrase = QString("test123").toLatin1();
    openOK = QSslCertificate::importPkcs12(&keyFile, &key, &certs, &caCerts, passPhrase);
    keyFile.close();
    

    相关文章

      网友评论

      • 423138054120:博主你好,最近碰到一个问题,也是qt证书的,可是我的认证只需要本地证书,没有密钥的,就和浏览器访问某些特殊网站需要手动选择证书的那种一样,证书是已经安装注册进电脑,只是使用时调用一下,可是我现在尝试了各种办法,都加载不了证书,偶然的一次fiddler抓包,fiddler提示把证书放到fiddler的一个目录下,结果fiddler居然可以代理请求了。然后msvc版的network模块正好可以被fiddler抓包和代理,然后现在采用的迂回方式,qt程序里发送请求但是必须开着fiddler,关了就发不了。没有办法直接程序里成功发送,试过QList<QSslCertificate>这些类似的写法。
        ArcDriver:你好,从你的描述来看应该是想实现对端接收端对发送端的认证,可以试试从对端抓包看看有没有qt提供的认证信息。
        一般来说发送端如果要提供本地认证信息,证书和密钥都是成对生成的,如果想更方便一些的话,可以试试使用p12格式的密钥库(需要qt5.4以上版本)
        423138054120:有没有办法只加载本地证书的,.cer.pem.crt文件这些。

      本文标题:QSslSocket双向认证设置

      本文链接:https://www.haomeiwen.com/subject/crpmtftx.html