69.Qt 实现Https ssl证书与内网服务器ip进行双向认证

2026-05-11 11:40:14108 阅读量

参考https://blog.csdn.net/qq_38502914/article/details/124630727
 

相关服务:美国站群服务器

1.安装OpenSSL

下载地址: Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions

安装到第二个选项时,选择安装到指定的/bin目录下。

 下载好后将两个dll拷贝到:

然后在qt pro里导入:

LIBS += -LD:\OpenSSL-Win64\lib -llibcrypto -llibssl

INCLUDEPATH += D:\OpenSSL-Win64\include

如果在公司项目中,则需要自己将dll和libs还有include拷贝到工程里,指定生成exe路径

2.SSL证书介绍

常见的SSL证书文件后缀扩展名说明:

*.DER或*.CER文件: 带有这类后缀扩展名的SSL证书文件是二进制格式,只含有SSL证书信息,不包含私钥。

*.CRT文件: 这样的证书文件可以是二进制格式,也可以是文本格式,一般均为文本格式,功能与 *.DER及*.CER证书文件相同。

*.PEM文件: 这样的证书文件一般是文本格式,可以存放证书或私钥,或者两者都包含。 *.PEM 文件如果只包含私钥,一般用*.KEY文件代替。

*.PFX或*.P12文件: 这样的证书文件是二进制格式,同时包含证书和私钥,且一般有密码保护。

之所以使用证书,是确保服务器地址有效性,主要是通过CA证书与服务器证书进行对比,也可以实现数据传输的加密处理,避免被劫持和篡改

3.创建SSL证书

*.csr文件:证书签名请求文件

*.key文件:私钥文件

*.crt文件:证书文件

生成私钥和证书

// CN非常很重要,必须和请求url的地址保持一致
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 3650 -out server.crt -subj "/C=CN/ST=Shanghai/L=Shanghai/O=xxx/OU=xxx/CN=10.0.1.160/[email protected]"

req:  配置参数-x509指定使用 X.509证书签名请求管理(certificate signing request (CSR))."X.509" 是一个公钥代表that SSL and TLS adheres to for its key and certificate management.
-nodes:  告诉OpenSSL生产证书时忽略密码环节.(因为我们需要Nginx自动读取这个文件,而不是以用户交互的形式)。
-days 36500:  证书有效期,100年
-newkey rsa:  2048: 同时产生一个新证书和一个新的SSL key(加密强度为RSA 2048)
-keyout:  SSL输出文件名
-out:  证书生成文件名

C : 地区,如中国为 CN,美国为 US
CT: 省份
L: 所在地的市/县/区
O: 填单位/机构/企业合法的名称,比如baidu
OU: 填部门名称
CN: 填域名,由于我们是ip访问,所以填ip,否则填域名,我们在验证服务器证书时,会验证Common Name与http请求的url域名是否匹配,如果不匹配,ssl会返回错误。
emailAddress: 邮件地址

生成了server.crt  server.key
nginx开启ssl模块

由于我这里开启的,所以跳过

生成客户端证书

执行命令:

openssl genrsa -out client.key 2048

openssl req -new -key client.key -out client.csr
Country Name (2 letter code) [AU]:CN		// 如中国为 CN,美国为 US
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai			// 填您所在地的市/县/区
Organization Name (eg, company) [Internet Widgits Pty Ltd]:puniu		// 填单位/机构/企业合法的名称
Organizational Unit Name (eg, section) []:xxxClient		// 填部门名称
Common Name (e.g. server FQDN or YOUR name) []:xxxClient  // 填域名,客户端随便填
Email Address []: [email protected]	 // 邮件地址,可以不必输入,按回车跳过
 
A challenge password []:      # 私钥保护密码,可直接回车
An optional company name []:    # 一个可选公司名称,可直接回车


openssl x509 -req -days 3650 -in client.csr -signkey client.key -out client.crt

然后得到: client.key client.csr client.crt
 

配置nginx

服务器只需要服务器证书和私钥以及客户端证书即可
打开配置文件 nano /etc/nginx/nginx.conf:
修改如下所示:

server {
        listen       8081;
        listen       443 ssl;            # 监听443端口, 开启ssl(必须)
        listen       [::]:8081;
        server_name  10.0.1.160;  # 必须和服务器证书CN值保持一致,否则会返回ssl错误
        root         /usr/share/nginx/html;
                
        ssl on;
        # 引用ssl证书(必须,如果放在nginx/conf/ssl下可以用相对路径,其他位置必须用绝对路径)
        ssl_certificate  /etc/pki/CA/lisense/server.crt;
        ssl_certificate_key  /etc/pki/CA/lisense/server.key;
        ssl_client_certificate /etc/pki/CA/lisense/client.crt;
        ssl_verify_client on;                  # 开启客户端传输

        # 协议优化(可选,优化https协议,增强安全性)
        ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers          HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache    shared:SSL:10m;
        ssl_session_timeout  10m;

        
        //...其它配置
      }

更新服务器配置
nginx -t      // 查看配置,返回ok,则可以更新配置
nginx -s reload  // 更新配置
注:记得开启防火墙的443端口 firewall-cmd --zone=public --add_port=443/tcp permanent

4.QT客户端使用https访问服务器验证

    qDebug() << "支持OpenSSL: " << QSslSocket::supportsSsl();

    // 客户端证书
    QFile crtFile("D:/OpenSSL-Win64/bin/client.crt");
    crtFile.open(QIODevice::ReadOnly);
    const QSslCertificate certificate(&crtFile, QSsl::Pem);
    crtFile.close();

    // 客户端私钥
    QFile keyFile("D:/OpenSSL-Win64/bin/client.key");
    keyFile.open(QIODevice::ReadOnly);
    const QSslKey prvateKey(&keyFile, QSsl::Rsa);
    keyFile.close();

    // 通过QSslConfiguration 类进行SSL连接配置
    QSslConfiguration config ;
    //设置SSL验证模式 客户端与服务器进行双向校验
    config.setPeerVerifyMode(QSslSocket::VerifyPeer);
    //使用TLS 1.2 协议版本 和服务器保持一致
    config.setProtocol(QSsl::TlsV1_2);
    config.setPrivateKey(prvateKey);
    config.setLocalCertificate(certificate);

    //由于是自签名的服务器端证书,我们还得将服务器端的证书叫入到CA证书数据库中,否则服务器端证书将验证失败。
    QList<QSslCertificate> caCerList;
    QFile fileServCrt("D:/OpenSSL-Win64/bin/server.crt");
    fileServCrt.open(QIODevice::ReadOnly);
    const QSslCertificate cACertificate(&fileServCrt, QSsl::Pem);

    //将服务证书加入到CA列表中
    caCerList << cACertificate;
    config.setCaCertificates(caCerList);

    //网络连接管路
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);

    QUrl url(QUrl("https://10.0.1.160:443/static/SSD/20220621/123.txt"));
    QNetworkRequest request(url);

    //加入ssl配置信息
    request.setSslConfiguration(config);
    //设置rest api 数据内容为json格式
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    connect(manager, &QNetworkAccessManager::finished, this, [](QNetworkReply* reply){

        QByteArray byteArray = reply->readAll();

        qDebug()<<"finished:"<<reply->error()<<byteArray.length()<<byteArray;

        QSslCertificate cert = reply->sslConfiguration().peerCertificate();

        qDebug()<<"打印服务器证书:";

        qDebug()<<cert.isNull();
        qDebug()<<cert.issuerDisplayName();
        qDebug()<<cert.subjectDisplayName();
        qDebug()<<cert.version();
        qDebug()<<"serialNumber"<<cert.serialNumber();
        qDebug()<<cert.issuerInfo(QSslCertificate::Organization);
        qDebug()<<"CommonName: "<<cert.issuerInfo(QSslCertificate::CommonName);
        qDebug()<<cert.issuerInfo(QSslCertificate::LocalityName);
        qDebug()<<cert.issuerInfo(QSslCertificate::OrganizationalUnitName);
        qDebug()<<cert.issuerInfo(QSslCertificate::CountryName);
        qDebug()<<cert.issuerInfo(QSslCertificate::StateOrProvinceName);
        qDebug()<<cert.issuerInfo(QSslCertificate::DistinguishedNameQualifier);
        qDebug()<<cert.issuerInfo(QSslCertificate::SerialNumber);
        qDebug()<<cert.issuerInfo(QSslCertificate::EmailAddress);

        qDebug()<<cert.effectiveDate();
        qDebug()<<cert.expiryDate();

    });

    connect(manager, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply*reply,const QList<QSslError>& errors){

        qDebug()<<"sslErrors:"<<errors;

    });


    manager->get(request);

总结

如果CA证书和服务器证书不匹配,则报错:
sslErrors: ("The certificate is self-signed, and untrusted")


如果客户端证书与服务器绑定的客户端证书不匹配,则报错:
QNetworkReply::UnknownNetworkError

如果忽略SSL错误,可以连接QNetworkAccessManager::sslErrors信号槽,然后在槽函数ignoreSslErrors即可.

如果是web浏览器,则重写QWebEnginePage类,重写下面函数即可:

[virtual protected] bool QWebEnginePage::certificateError(const QWebEngineCertificateError &certificateError)

本文地址:https://www.idc504.com/news/9_12885.html