QSslCertificate: add fromFile() method

QSslCertificate::fromPath() does some extra work:
- matching wildcard glob or regular expression patterns
- checks if the string it's called on is a file or a dir

That extra work isn't needed when you already have the path to a
specific certificate file.
E.g. qtlsbackend_openssl.cpp:systemCaCertificates() used to call
fromPath() on *.pem/*.crt files that it got from iterating over system
certifcates dirs.

This also de-duplicates the code in fromPath().

[ChangeLog][QtNetwork][QSslCertificate] Added fromFile() method.

Change-Id: I92ab358e4711866dd4510da42c47905c7dae58b1
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Ahmad Samir 2025-04-30 17:30:42 +03:00
parent 2875c4358b
commit 2be51c6923
4 changed files with 82 additions and 19 deletions

View File

@ -633,6 +633,9 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
if (path.isEmpty())
return {};
if (syntax == PatternSyntax::FixedString && QFileInfo(path).isFile())
return fromFile(path, format);
// $, (,), *, +, ., ?, [, ,], ^, {, | and }.
// make sure to use the same path separators on Windows and Unix like systems.
@ -665,15 +668,8 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
pathPrefix = {};
} else {
// Check if the path is a file.
if (QFileInfo(sourcePath).isFile()) {
QFile file(sourcePath);
QIODevice::OpenMode openMode = QIODevice::ReadOnly;
if (format == QSsl::Pem)
openMode |= QIODevice::Text;
if (file.open(openMode))
return QSslCertificate::fromData(file.readAll(), format);
return QList<QSslCertificate>();
}
if (QFileInfo(sourcePath).isFile())
return fromFile(sourcePath, format);
}
// Special case - if the prefix ends up being nothing, use "." instead.
@ -710,12 +706,7 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
continue;
#endif
QFile file(filePath);
QIODevice::OpenMode openMode = QIODevice::ReadOnly;
if (format == QSsl::Pem)
openMode |= QIODevice::Text;
if (file.open(openMode))
certs += QSslCertificate::fromData(file.readAll(), format);
certs += QSslCertificate::fromFile(filePath, format);
}
return certs;
}
@ -760,6 +751,30 @@ QList<QSslCertificate> QSslCertificate::fromData(const QByteArray &data, QSsl::E
return reader(data, -1);
}
/*!
\since 6.10
Reads the data from the file \a filePath and parses all certificates
that are encoded in the specified \a format and returns a list of
QSslCertificate objects.
If \a filePath isn't a regular file, this method will return an empty
list.
\sa fromData(), fromPath()
*/
QList<QSslCertificate> QSslCertificate::fromFile(const QString &filePath,
QSsl::EncodingFormat format)
{
QFile file(filePath);
QIODevice::OpenMode openMode = QIODevice::ReadOnly;
if (format == QSsl::Pem)
openMode |= QIODevice::Text;
if (file.open(openMode))
return QSslCertificate::fromData(file.readAll(), format);
return {};
}
#ifndef QT_NO_SSL
/*!
Verifies a certificate chain. The chain to be verified is passed in the

View File

@ -107,6 +107,8 @@ public:
QIODevice *device, QSsl::EncodingFormat format = QSsl::Pem);
static QList<QSslCertificate> fromData(
const QByteArray &data, QSsl::EncodingFormat format = QSsl::Pem);
static QList<QSslCertificate> fromFile(
const QString &filePath, QSsl::EncodingFormat format = QSsl::Pem);
#ifndef QT_NO_SSL
static QList<QSslError> verify(const QList<QSslCertificate> &certificateChain, const QString &hostName = QString());

View File

@ -410,7 +410,7 @@ QList<QSslCertificate> systemCaCertificates()
}
}
for (const QString& file : std::as_const(certFiles))
systemCerts.append(QSslCertificate::fromPath(file, QSsl::Pem));
systemCerts.append(QSslCertificate::fromFile(file, QSsl::Pem));
}
#endif // platform
#ifdef QSSLSOCKET_DEBUG

View File

@ -20,6 +20,8 @@
#include <openssl/obj_mac.h>
#endif
using namespace Qt::StringLiterals;
class tst_QSslCertificate : public QObject
{
Q_OBJECT
@ -97,6 +99,9 @@ private slots:
// helper for verbose test failure messages
QString toString(const QList<QSslError>&);
private:
void certInfo_helper(const char *methodName);
// ### add tests for certificate bundles (multiple certificates concatenated into a single
// structure); both PEM and DER formatted
#endif // QT_CONFIG(ssl)
@ -494,12 +499,23 @@ void tst_QSslCertificate::subjectIssuerDisplayName()
QFETCH(const QString, certName);
QFETCH(const QString, expectedName);
{
const auto chain = QSslCertificate::fromPath(testDataDir + certName);
QCOMPARE(chain.size(), 1);
const auto cert = chain.at(0);
QVERIFY(!cert.isNull());
QCOMPARE(cert.subjectDisplayName(), expectedName);
QCOMPARE(cert.issuerDisplayName(), expectedName);
}
{
const auto chain = QSslCertificate::fromFile(testDataDir + certName);
QCOMPARE(chain.size(), 1);
const auto cert = chain.at(0);
QVERIFY(!cert.isNull());
QCOMPARE(cert.subjectDisplayName(), expectedName);
QCOMPARE(cert.issuerDisplayName(), expectedName);
}
}
void tst_QSslCertificate::utf8SubjectNames()
@ -701,9 +717,14 @@ void tst_QSslCertificate::fromPath_qregularexpression()
pemencoding ? QSsl::Pem : QSsl::Der,
QSslCertificate::PatternSyntax(syntax)).size(),
numCerts);
if (QSslCertificate::PatternSyntax(syntax) == QSslCertificate::PatternSyntax::FixedString) {
const auto list = QSslCertificate::fromFile(path, pemencoding ? QSsl::Pem : QSsl::Der);
QCOMPARE(list.size(), numCerts);
}
}
void tst_QSslCertificate::certInfo()
void tst_QSslCertificate::certInfo_helper(const char *methodName)
{
// MD5 Fingerprint=B6:CF:57:34:DA:A9:73:21:82:F7:CF:4D:3D:85:31:88
// SHA1 Fingerprint=B6:D1:51:82:E0:29:CA:59:96:38:BD:B6:F9:40:05:91:6D:49:09:60
@ -788,8 +809,14 @@ void tst_QSslCertificate::certInfo()
"dc:c2:eb:b7:bb:50:18:05:ba:ad:af:08:49:fe:98:63"
"55:ba:e7:fb:95:5d:91";
QSslCertificate cert = QSslCertificate::fromPath(testDataDir + "certificates/cert.pem", QSsl::Pem,
QSslCertificate::PatternSyntax::FixedString).first();
QSslCertificate cert;
if (methodName == "fromPath"_L1) {
cert = QSslCertificate::fromPath(testDataDir + "certificates/cert.pem", QSsl::Pem,
QSslCertificate::PatternSyntax::FixedString).first();
} else if (methodName == "fromFile"_L1) {
cert = QSslCertificate::fromFile(testDataDir + "certificates/cert.pem", QSsl::Pem).first();
}
QVERIFY(!cert.isNull());
QCOMPARE(cert.issuerInfo(QSslCertificate::Organization)[0], QString("CryptSoft Pty Ltd"));
@ -845,8 +872,15 @@ void tst_QSslCertificate::certInfo()
QCOMPARE(cert, QSslCertificate(QByteArray::fromHex(der), QSsl::Der));
}
void tst_QSslCertificate::certInfo()
{
certInfo_helper("fromPath");
certInfo_helper("fromFile");
}
void tst_QSslCertificate::certInfoQByteArray()
{
{
QSslCertificate cert = QSslCertificate::fromPath(testDataDir + "certificates/cert.pem", QSsl::Pem,
QSslCertificate::PatternSyntax::FixedString).first();
QVERIFY(!cert.isNull());
@ -855,6 +889,18 @@ void tst_QSslCertificate::certInfoQByteArray()
// we fixed a bug we had with lazy initialization of the values.
QCOMPARE(cert.issuerInfo("CN")[0], QString("Test CA (1024 bit)"));
QCOMPARE(cert.subjectInfo("CN")[0], QString("name/with/slashes"));
}
{
QSslCertificate cert =
QSslCertificate::fromFile(testDataDir + "certificates/cert.pem", QSsl::Pem).first();
QVERIFY(!cert.isNull());
// in this test, check the bytearray variants before the enum variants to see if
// we fixed a bug we had with lazy initialization of the values.
QCOMPARE(cert.issuerInfo("CN")[0], QString("Test CA (1024 bit)"));
QCOMPARE(cert.subjectInfo("CN")[0], QString("name/with/slashes"));
}
}
void tst_QSslCertificate::task256066toPem()