Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 131 additions & 8 deletions client/LdapSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,20 @@
#include <QtNetwork/QSslCertificate>

#ifdef Q_OS_WIN
#include <Windows.h>
#include <QtCore/QScopeGuard>
#include <qt_windows.h>
#include <Winldap.h>
#include <Winber.h>
#include <wincrypt.h>

using STR_T = PWSTR;
#define STR(X) const_cast<STR_T>(L##X)
#define TO_STR(X) STR_T((X).utf16())
#else
#define LDAP_DEPRECATED 1
#include <sys/time.h>
#include <ldap.h>

using ULONG = int;
using LDAP_TIMEVAL = timeval;
using STR_T = char *;
Expand All @@ -57,21 +61,130 @@ static constexpr auto TO_QSTR(const T *str)
return QStringView(str);
}

class LdapSearch::Private
struct LdapSearch::Private
{
public:
#ifdef Q_OS_WIN
static thread_local const Private *activeInstance;
static BOOLEAN LDAPAPI verifyServerCertificate(PLDAP, PCCERT_CONTEXT *serverCert);
~Private() { if(trustStore) CertCloseStore(trustStore, 0); }
HCERTSTORE trustStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, nullptr);
#else
QByteArray caCertPath;
#endif
LDAP *ldap {};
QUrl url;
QByteArray caCertPath;
QTimer *timer {};
};

LdapSearch::LdapSearch(const QString &url, const QString &caCertPath, QObject *parent)
#ifdef Q_OS_WIN
thread_local const LdapSearch::Private *LdapSearch::Private::activeInstance {};

BOOLEAN LDAPAPI LdapSearch::Private::verifyServerCertificate(PLDAP, PCCERT_CONTEXT *serverCert) try
{
PCCERT_CONTEXT certificate = serverCert ? *serverCert : nullptr;
const auto freeCertificate = qScopeGuard([certificate] {
if(certificate)
CertFreeCertificateContext(certificate);
});

const Private *d = activeInstance;
if(!certificate || !d || !d->trustStore)
return FALSE;

CERT_CHAIN_ENGINE_CONFIG engineConfig {
.cbSize = sizeof(engineConfig),
.hExclusiveRoot = d->trustStore,
};
HCERTCHAINENGINE engine {};
if(!CertCreateCertificateChainEngine(&engineConfig, &engine))
return FALSE;
const auto freeEngine = qScopeGuard([engine] {
CertFreeCertificateChainEngine(engine);
});

LPSTR serverAuthOID = const_cast<LPSTR>(szOID_PKIX_KP_SERVER_AUTH);
CERT_CHAIN_PARA chainPara {
.cbSize = sizeof(chainPara),
.RequestedUsage {
.dwType = USAGE_MATCH_TYPE_OR,
.Usage {
.cUsageIdentifier = 1,
.rgpszUsageIdentifier = &serverAuthOID,
},
},
};

PCCERT_CHAIN_CONTEXT chainContext {};
BOOL built = CertGetCertificateChain(engine, certificate, nullptr, certificate->hCertStore,
&chainPara, CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, nullptr, &chainContext);
const auto freeChain = qScopeGuard([&chainContext] {
if(chainContext)
CertFreeCertificateChain(chainContext);
});

BOOLEAN accepted = FALSE;
if(built && chainContext)
{
constexpr DWORD softFailMask = CERT_TRUST_IS_OFFLINE_REVOCATION | CERT_TRUST_REVOCATION_STATUS_UNKNOWN;
if((chainContext->TrustStatus.dwErrorStatus & ~softFailMask) == 0)
{
const QString host = d->url.host();
HTTPSPolicyCallbackData sslPolicy {
.cbStruct = sizeof(sslPolicy),
.dwAuthType = AUTHTYPE_SERVER,
.fdwChecks = 0,
.pwszServerName = TO_STR(host),
};

CERT_CHAIN_POLICY_PARA policyPara {
.cbSize = sizeof(policyPara),
.pvExtraPolicyPara = &sslPolicy,
};

CERT_CHAIN_POLICY_STATUS policyStatus {
.cbSize = sizeof(policyStatus),
};

if(CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext, &policyPara, &policyStatus) &&
(policyStatus.dwError == 0 ||
policyStatus.dwError == DWORD(CRYPT_E_NO_REVOCATION_CHECK) ||
policyStatus.dwError == DWORD(CRYPT_E_REVOCATION_OFFLINE)))
accepted = TRUE;
}
}
return accepted;
}
catch(...)
{
return FALSE;
}
#endif

LdapSearch::LdapSearch(const QString &url,
#ifdef Q_OS_WIN
const QList<QSslCertificate> &caCerts,
#else
const QString &caCertPath,
#endif
QObject *parent)
: QObject( parent )
, d(new Private)
{
d->url = QUrl(url);
#ifdef Q_OS_WIN
if(d->trustStore)
{
for(const QSslCertificate &cert: caCerts)
{
QByteArray der = cert.toDer();
CertAddEncodedCertificateToStore(d->trustStore, X509_ASN_ENCODING,
reinterpret_cast<const BYTE *>(der.constData()), DWORD(der.size()),
CERT_STORE_ADD_ALWAYS, nullptr);
}
}
#else
d->caCertPath = QFile::encodeName(caCertPath);
#endif
d->timer = new QTimer(this);
d->timer->setSingleShot(true);
connect(d->timer, &QTimer::timeout, this, [this]{
Expand Down Expand Up @@ -105,6 +218,10 @@ bool LdapSearch::init()
setLastError(tr("Failed to init ldap"), int(LdapGetLastError()));
return false;
}

if(ssl && d->trustStore)
ldap_set_option(d->ldap, LDAP_OPT_SERVER_CERTIFICATE, &Private::verifyServerCertificate);

ULONG err = 0;
#else
if(!d->caCertPath.isEmpty())
Expand Down Expand Up @@ -138,9 +255,16 @@ bool LdapSearch::init()
return false;
}

if(auto err = ldap_simple_bind_s(d->ldap, nullptr, nullptr))
#ifdef Q_OS_WIN
Private::activeInstance = d;
#endif
auto bindErr = ldap_simple_bind_s(d->ldap, nullptr, nullptr);
#ifdef Q_OS_WIN
Private::activeInstance = nullptr;
#endif
if(bindErr)
{
setLastError(tr("Failed to init ldap"), err);
setLastError(tr("Failed to init ldap"), bindErr);
return false;
}

Expand Down Expand Up @@ -228,4 +352,3 @@ void LdapSearch::setLastError( const QString &msg, int err )
}
Q_EMIT error( res, details );
}

6 changes: 5 additions & 1 deletion client/LdapSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ class LdapSearch final: public QObject
Q_OBJECT

public:
#ifdef Q_OS_WIN
LdapSearch(const QString &url, const QList<QSslCertificate> &caCerts = {}, QObject *parent = nullptr);
#else
LdapSearch(const QString &url, const QString &caCertPath = {}, QObject *parent = nullptr);
#endif
~LdapSearch() final;

void search(const QString &search, const QVariantMap &userData);
Expand All @@ -40,6 +44,6 @@ class LdapSearch final: public QObject
bool init();
void setLastError( const QString &msg, int err );

class Private;
struct Private;
Private *d;
};
28 changes: 19 additions & 9 deletions client/dialogs/AddRecipients.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,31 @@ AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent)
: QDialog(parent)
, ui(new Ui::AddRecipients)
{
#ifndef Q_OS_WIN
if(const auto list = Application::confValue(QLatin1String("LDAP-CERTS")).toArray();
!list.isEmpty() && ldapCACerts.open())
#ifdef Q_OS_WIN
QList<QSslCertificate> ldapTrust;
for(const auto &entry: Application::confValue(QLatin1String("LDAP-CERTS")).toArray())
{
if(QSslCertificate cert(QByteArray::fromBase64(entry.toString().toLatin1()), QSsl::Der); !cert.isNull())
ldapTrust.append(cert);
}
#else
if(ldapCACerts.open())
{
for(const auto &entry : list)
ldapCACerts.write(QSslCertificate(QByteArray::fromBase64(entry.toString().toLatin1()), QSsl::Der).toPem());
for(const auto &entry: Application::confValue(QLatin1String("LDAP-CERTS")).toArray())
{
if(QSslCertificate cert(QByteArray::fromBase64(entry.toString().toLatin1()), QSsl::Der); !cert.isNull())
ldapCACerts.write(cert.toPem());
}
ldapCACerts.close();
}
const QString ldapTrust = ldapCACerts.fileName();
#endif
ldap_corp = new LdapSearch(Application::confValue(QLatin1String("LDAP-CORP-URL")).toString(QStringLiteral("ldaps://k3.ldap.sk.ee")), ldapCACerts.fileName(), this);
ldap_corp = new LdapSearch(Application::confValue(QLatin1String("LDAP-CORP-URL")).toString(QStringLiteral("ldaps://k3.ldap.sk.ee")), ldapTrust, this);
for(const auto list = Application::confValue(QLatin1String("LDAP-PERSON-URLS")).toArray(); auto url: list)
ldap_person.append(new LdapSearch(url.toString(), ldapCACerts.fileName(), this));
ldap_person.append(new LdapSearch(url.toString(), ldapTrust, this));
if(ldap_person.isEmpty()) {
ldap_person.append(new LdapSearch(QStringLiteral("ldaps://esteid.ldap.sk.ee"), ldapCACerts.fileName(), this));
ldap_person.append(new LdapSearch(QStringLiteral("ldaps://ldap.eidpki.ee/dc=eidpki,dc=ee"), ldapCACerts.fileName(), this));
ldap_person.append(new LdapSearch(QStringLiteral("ldaps://esteid.ldap.sk.ee"), ldapTrust, this));
ldap_person.append(new LdapSearch(QStringLiteral("ldaps://ldap.eidpki.ee/dc=eidpki,dc=ee"), ldapTrust, this));
}

ui->setupUi(this);
Expand Down
4 changes: 4 additions & 0 deletions client/dialogs/AddRecipients.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
#include "CertificateHistory.h"

#include <QDialog>
#ifndef Q_OS_WIN
#include <QtCore/QTemporaryFile>
#endif

namespace Ui {
class AddRecipients;
Expand Down Expand Up @@ -62,6 +64,8 @@ class AddRecipients final : public QDialog
LdapSearch *ldap_corp;
int multiSearch = 0;

#ifndef Q_OS_WIN
QTemporaryFile ldapCACerts;
#endif
HistoryList historyCertData;
};
Loading