Provide more details on pinned certificate files

This commit is contained in:
LordGrey 2024-12-08 20:13:05 +01:00
parent 733aa662bf
commit a8e9fe30fe
2 changed files with 36 additions and 28 deletions

View File

@ -17,7 +17,6 @@
#include <QSslSocket> #include <QSslSocket>
//std includes //std includes
#include <iostream>
#include <chrono> #include <chrono>
// Constants // Constants
@ -231,7 +230,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
_networkManager->setTransferTimeout(_requestTimeout.count()); _networkManager->setTransferTimeout(_requestTimeout.count());
#endif #endif
QDateTime start = QDateTime::currentDateTime(); QDateTime const start = QDateTime::currentDateTime();
QString opCode; QString opCode;
QNetworkReply* reply; QNetworkReply* reply;
@ -267,7 +266,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
// Go into the loop until the request is finished. // Go into the loop until the request is finished.
loop.exec(); loop.exec();
QDateTime end = QDateTime::currentDateTime(); QDateTime const end = QDateTime::currentDateTime();
httpResponse response = (reply->operation() == operation) ? getResponse(reply) : httpResponse(); httpResponse response = (reply->operation() == operation) ? getResponse(reply) : httpResponse();
@ -283,18 +282,18 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
{ {
httpResponse response; httpResponse response;
HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); HttpStatusCode const httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
response.setHttpStatusCode(httpStatusCode); response.setHttpStatusCode(httpStatusCode);
response.setNetworkReplyError(reply->error()); response.setNetworkReplyError(reply->error());
response.setHeaders(reply->rawHeaderPairs()); response.setHeaders(reply->rawHeaderPairs());
if (reply->error() == QNetworkReply::NoError) if (reply->error() == QNetworkReply::NoError)
{ {
QByteArray replyData = reply->readAll(); QByteArray const replyData = reply->readAll();
if (!replyData.isEmpty()) if (!replyData.isEmpty())
{ {
QJsonParseError error; QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(replyData, &error); QJsonDocument const jsonDoc = QJsonDocument::fromJson(replyData, &error);
if (error.error != QJsonParseError::NoError) if (error.error != QJsonParseError::NoError)
{ {
@ -322,7 +321,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
else else
{ {
if (httpStatusCode > 0) { if (httpStatusCode > 0) {
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); QString const httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
QString advise; QString advise;
switch ( httpStatusCode ) { switch ( httpStatusCode ) {
case HttpStatusCode::BadRequest: case HttpStatusCode::BadRequest:
@ -339,7 +338,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
break; break;
case HttpStatusCode::TooManyRequests: case HttpStatusCode::TooManyRequests:
{ {
QString retryAfterTime = response.getHeader("Retry-After"); QString const retryAfterTime = response.getHeader("Retry-After");
if (!retryAfterTime.isEmpty()) if (!retryAfterTime.isEmpty())
{ {
advise = "Retry-After: " + response.getHeader("Retry-After"); advise = "Retry-After: " + response.getHeader("Retry-After");
@ -366,7 +365,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
void ProviderRestApi::setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value) void ProviderRestApi::setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value)
{ {
QVariant headerValue = _networkRequestHeaders.header(header); QVariant const headerValue = _networkRequestHeaders.header(header);
if (headerValue.isNull()) if (headerValue.isNull())
{ {
_networkRequestHeaders.setHeader(header, value); _networkRequestHeaders.setHeader(header, value);
@ -394,7 +393,7 @@ void httpResponse::setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs)
} }
} }
QByteArray httpResponse::getHeader(const QByteArray header) const QByteArray httpResponse::getHeader(const QByteArray& header) const
{ {
return _responseHeaders.value(header); return _responseHeaders.value(header);
} }
@ -412,7 +411,7 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName)
return false; return false;
} }
QSslCertificate cert (&caFile); QSslCertificate const cert (&caFile);
caFile.close(); caFile.close();
QList<QSslCertificate> allowedCAs; QList<QSslCertificate> allowedCAs;
@ -424,8 +423,8 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName)
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
if (QSslSocket::supportsSsl()) if (QSslSocket::supportsSsl())
{ {
QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors, Qt::UniqueConnection ); QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors );
_networkManager->connectToHostEncrypted(_apiUrl.host(), _apiUrl.port(), configuration); _networkManager->connectToHostEncrypted(_apiUrl.host(), static_cast<quint16>(_apiUrl.port()), configuration);
rc = true; rc = true;
} }
#endif #endif
@ -453,8 +452,8 @@ bool ProviderRestApi::checkServerIdentity(const QSslConfiguration& sslConfig) co
bool isServerIdentified {false}; bool isServerIdentified {false};
// Perform common name validation // Perform common name validation
QSslCertificate serverCertificate = sslConfig.peerCertificate(); QSslCertificate const serverCertificate = sslConfig.peerCertificate();
QStringList commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName); QStringList const commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName);
if ( commonName.contains(getAlternateServerIdentity(), Qt::CaseInsensitive) ) if ( commonName.contains(getAlternateServerIdentity(), Qt::CaseInsensitive) )
{ {
isServerIdentified = true; isServerIdentified = true;
@ -467,27 +466,36 @@ bool ProviderRestApi::matchesPinnedCertificate(const QSslCertificate& certificat
{ {
bool isMatching {false}; bool isMatching {false};
QList certificateInfos = certificate.subjectInfo(QSslCertificate::CommonName); QList const certificateInfos = certificate.subjectInfo(QSslCertificate::CommonName);
if (certificateInfos.isEmpty()) if (certificateInfos.isEmpty())
{ {
return false; return false;
} }
QString identifier = certificateInfos.constFirst(); QString const& identifier = certificateInfos.constFirst();
QString const appDataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QString const certDir = appDataDir + "/certificates";
QString appDataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); Debug (_log,"Directory used for pinned certificates: %s", QSTRING_CSTR(certDir));
QString certDir = appDataDir + "/certificates";
QDir().mkpath(certDir);
QString filePath(certDir + "/" + identifier + ".pem"); bool const isMkPath = QDir().mkpath(certDir);
if (!isMkPath)
{
Error (_log,"Failed to create directory \"%s\" to store pinned certificates. 'Trust on first use' is rejected.", QSTRING_CSTR(certDir));
return false;
}
QString const fileName(identifier + ".pem");
QString const filePath(certDir + "/" + fileName);
QFile file(filePath); QFile file(filePath);
if (file.open(QIODevice::ReadOnly)) if (file.open(QIODevice::ReadOnly))
{ {
QList certificates = QSslCertificate::fromDevice(&file, QSsl::Pem); QList const certificates = QSslCertificate::fromDevice(&file, QSsl::Pem);
if (!certificates.isEmpty()) if (!certificates.isEmpty())
{ {
Debug (_log,"First used certificate loaded successfully"); Debug (_log,"First used certificate \"%s\" loaded successfully", QSTRING_CSTR(fileName));
QSslCertificate pinnedeCertificate = certificates.constFirst(); QSslCertificate const& pinnedeCertificate = certificates.constFirst();
if (pinnedeCertificate == certificate) if (pinnedeCertificate == certificate)
{ {
isMatching = true; isMatching = true;
@ -503,8 +511,8 @@ bool ProviderRestApi::matchesPinnedCertificate(const QSslCertificate& certificat
{ {
if (file.open(QIODevice::WriteOnly)) if (file.open(QIODevice::WriteOnly))
{ {
QByteArray pemData = certificate.toPem(); QByteArray const pemData = certificate.toPem();
qint64 bytesWritten = file.write(pemData); qint64 const bytesWritten = file.write(pemData);
if (bytesWritten == pemData.size()) if (bytesWritten == pemData.size())
{ {
Debug (_log,"First used certificate saved to file: %s", QSTRING_CSTR(filePath)); Debug (_log,"First used certificate saved to file: %s", QSTRING_CSTR(filePath));
@ -538,7 +546,7 @@ void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList<QSslError>&
if (_isSeflSignedCertificateAccpeted) if (_isSeflSignedCertificateAccpeted)
{ {
// Get the peer certificate associated with the error // Get the peer certificate associated with the error
QSslCertificate certificate = error.certificate(); QSslCertificate const certificate = error.certificate();
if (matchesPinnedCertificate(certificate)) if (matchesPinnedCertificate(certificate))
{ {
Debug (_log,"'Trust on first use' - Certificate received matches pinned certificate"); Debug (_log,"'Trust on first use' - Certificate received matches pinned certificate");

View File

@ -89,7 +89,7 @@ public:
void setBody(const QJsonDocument& body) { _responseBody = body; } void setBody(const QJsonDocument& body) { _responseBody = body; }
QByteArray getHeader(const QByteArray header) const; QByteArray getHeader(const QByteArray& header) const;
void setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs); void setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs);
QString getErrorReason() const { return _errorReason; } QString getErrorReason() const { return _errorReason; }