前面介绍了加载证书及解析证书链,接下来介绍证书验证
证书验证
Qt提供证书验证的接口
static QList<QSslError> verify(QList<QSslCertificate> certificateChain, const QString &hostName = QString());
- 传参证书链,证书链可以不用根证书,验的时候会到系统证书库中找
- 域名验证可选
如果已经有了证书链,那就好办,直接验就可以了
那如果只知道用户证书,不知道上一级证书呢?当然是先取证书链,取到再验
证书作为公开的信息,通过相关途径可以找到关联的证书,方法如下
以百度证书为例,查看证书,找到扩展项授权信息访问
授权信息访问提供了上一级证书的下载地址,通过浏览器打开链接即可下载
接下来查看该证书是否也提供了上一级证书的下载地址
可以看到,并没有提供,那就无法直接获取到上一级证书,也就是根证书
对于使用Qt验证的接口其实不用根证书也能验,因为该根证书是预置在系统证书库中,是受信任的根
既然知道该证书在系统库中,那我们是不是可以取到呢
答案当然是肯定的
通过以下接口可以取到系统库中的证书
QSslSocket::systemCaCertificates();
//或
QSslSocket::defaultCaCertificates();
系统库中这么多证书,那要怎么知道是对应哪个跟证书呢
证书扩展项中有两个字段,其中使用者密钥标识符是作为证书自己的标识符,授权密钥标识符是上一级证书的标识符
意思就是根证书的使用者密钥标识符和签发的下一级证书的授权密钥标识符是相等的
我们就可以通过这个来找出根证书
Code
废话不多说,代码才是硬道理
#include "rddownloadcertchain.h"
#include "httpgetmanager.h"
#include <QSslCertificate>
#include <QSslCertificateExtension>
#include <QSslSocket>
#include <QFile>
RdDownloadCertChain::RdDownloadCertChain(QObject *parent):
QObject(parent)
{
m_pHttpGetManager = new HttpGetManager(this);
// connect(m_pHttpGetManager, &HttpGetManager::sigGetFinish, this, &RdDownloadCertChain::onGetFinish);
}
RdDownloadCertChain::~RdDownloadCertChain()
{
delete m_pHttpGetManager;
}
int RdDownloadCertChain::dowmloadChain(const QByteArray &certData, QList<QByteArray> &chain)
{
QSslCertificate cert(certData);
if(cert.isNull()) {
cert.clear();
cert = QSslCertificate(certData, QSsl::Der);
}
if(cert.isNull()) {
return -1;
}
chain.append(cert.toPem());
QString url = this->getDownloadUrl(cert);
qDebug() << __FUNCTION__ << "song" << "url" << url;
if(!url.isEmpty()) {
m_pHttpGetManager->setUrl(url);
QByteArray data = m_pHttpGetManager->httpGet();
//递归下载
if(dowmloadChain(data, chain) != 0) {
return -1;
}
}
else {
QList<QSslCertificateExtension> exts = cert.extensions();
QString authKeyID, subjectKeyID;
foreach (auto ext, exts) {
//获取使用者密钥标识符和颁发者密钥标识符
if(ext.oid() == "2.5.29.35") {
authKeyID = ext.value().toMap().value("keyid").toString();
}
else if(ext.oid() == "2.5.29.14") {
subjectKeyID = ext.value().toString().replace(":", "").toLower();
}
}
//使用者密钥标识符和颁发者密钥标识符一样则为根证书
if(authKeyID != subjectKeyID) {
QList<QSslCertificate> sysCerts = QSslSocket::systemCaCertificates();
foreach (auto sysCert, sysCerts) {
QString subKeyID = getSubjectKeyid(sysCert);
if(subKeyID == authKeyID) {
chain.append(sysCert.toPem());
//找到直接结束,系统库可能会存在多个相同的证书
return 0;
}
}
}
}
return 0;
}
QString RdDownloadCertChain::getDownloadUrl(const QSslCertificate &cert)
{
QString url;
QList<QSslCertificateExtension> exts = cert.extensions();
foreach (auto ext, exts) {
//通过扩展项授权信息访问获取上一级证书的下载地址
if(ext.oid() == "1.3.6.1.5.5.7.1.1") {
QMap<QString, QVariant> extMap = ext.value().toMap();
QMap<QString, QVariant>::iterator iter = extMap.find("caIssuers");
if(iter != extMap.end()) {
url = iter.value().toString();
}
}
}
return url;
}
QString RdDownloadCertChain::getSubjectKeyid(const QSslCertificate &cert)
{
QString subKeyID;
QList<QSslCertificateExtension> sysExts = cert.extensions();
foreach (auto ext, sysExts) {
if(ext.oid() == "2.5.29.14") {
subKeyID = ext.value().toString().replace(":", "").toLower();
}
}
return subKeyID;
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "rddownloadcertchain.h"
#include <QFileDialog>
#include <QDebug>
#include <QSslError>
#include <QTreeWidget>
#include <QHeaderView>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_pDownladManager = new RdDownloadCertChain(this);
m_pTreeWidget = new QTreeWidget(this);
m_pTreeWidget->setGeometry(130, 130, 400, 150);
m_pTreeWidget->header()->setVisible(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::createTreeItem(const QStringList &chainNameList)
{
QTreeWidgetItem *rootItem = nullptr;
for(int i = chainNameList.size() -1; i >=0; --i) {
QString certName = chainNameList.at(i);
if(!rootItem) {
rootItem = new QTreeWidgetItem(m_pTreeWidget, QStringList(certName));
}
else {
rootItem = new QTreeWidgetItem(rootItem, QStringList(certName));
}
}
m_pTreeWidget->expandAll();
}
void MainWindow::on_pushButton_clicked()
{
QString url = QFileDialog::getOpenFileName(this);
ui->lineEdit->setText(url);
}
void MainWindow::on_pushButton_2_clicked()
{
m_sslChains.clear();
m_pTreeWidget->clear();
QFile file(ui->lineEdit->text());
if(!file.open(QIODevice::ReadOnly)) {
return;
}
QByteArray data = file.readAll();
file.close();
QList<QByteArray> chains;
m_pDownladManager->dowmloadChain(data,chains);
QStringList chainNameList;
foreach (auto certData, chains) {
QSslCertificate certtificate(certData);
m_sslChains.append(certtificate);
qDebug() << __FUNCTION__ << "song" << certtificate.subjectDisplayName();
chainNameList.append(certtificate.subjectDisplayName());
}
createTreeItem(chainNameList);
}
void MainWindow::on_pushButton_3_clicked()
{
QList<QSslError> errs = QSslCertificate::verify(m_sslChains);
if(errs.isEmpty()) {
ui->textEdit->setText(u8"此证书已通过验证");
}
else {
QString errText;
foreach (auto err, errs) {
if(!errText.isEmpty()) {
errText.append("\n");
}
qDebug() << __FUNCTION__ << "song" << "verify err:" << err.errorString();
errText.append(err.errorString());
}
ui->textEdit->setText(errText);
}
}
原理上面已经介绍了,代码量不多,以下是运行结果
Demo代码链接
https://download.csdn.net/download/a137748099/18718506
其他API
结合前两篇文章,QSslCertificate支持的接口基本都介绍完了
还剩下面的接口没介绍到
bool isBlacklisted() const; | 黑名单判别 |
bool isSelfSigned() const; | 自签证书判别,颁发者和使用者名称一样的证书为自签证书 |