Adds new feature: Auto-enable and host matching for client certificates

This commit is contained in:
Felix (xq) Queißner 2020-06-20 00:42:46 +02:00
parent bdfd6ba687
commit cf3b60ea29
7 changed files with 125 additions and 17 deletions

View File

@ -5,10 +5,9 @@ This document contains TODO items for planned Kristall releases as well as some
## 0.3 - TLS and security
- [ ] TLS Handling
- [ ] Add management for client certificates
- [ ] Rename/delete/merge groups
- [ ] Allow merge/delete/merge groups
- [ ] Import/export PEM certificates and keys
- [ ] Add a "scope" option to certificates so users can restrict the scope where the certificate is valid
## 0.4 - The colorful release
- [ ] Implement dual-colored icon theme
- [ ] Improve UX

View File

@ -767,18 +767,7 @@ bool BrowserTab::trySetClientCertificate(const QString &query)
return false;
}
this->current_identity = dialog.identity();
if (not current_identity.isValid())
{
QMessageBox::warning(this, "Kristall", "Failed to generate temporary crypto-identitiy");
this->disableClientCertificate();
return false;
}
this->ui->enable_client_cert_button->setChecked(true);
return true;
return this->enableClientCertificate(dialog.identity());
}
void BrowserTab::resetClientCertificate()
@ -826,7 +815,7 @@ bool BrowserTab::startRequest(const QUrl &url, ProtocolHandler::RequestOptions o
auto answer = QMessageBox::question(
this,
"Kristall",
QString("You requested a %1-URL with a client certificate, but these are not supported for this scheme. Continue?").arg(url.scheme())
tr("You requested a %1-URL with a client certificate, but these are not supported for this scheme. Continue?").arg(url.scheme())
);
if(answer != QMessageBox::Yes)
return false;
@ -840,7 +829,7 @@ bool BrowserTab::startRequest(const QUrl &url, ProtocolHandler::RequestOptions o
auto answer = QMessageBox::question(
this,
"Kristall",
"You want to visit a new host, but have a client certificate enabled. This may be a risk to expose your identity to another host.\r\nDo you want to keep the certificate enabled?",
tr("You want to visit a new host, but have a client certificate enabled. This may be a risk to expose your identity to another host.\r\nDo you want to keep the certificate enabled?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
@ -849,6 +838,59 @@ bool BrowserTab::startRequest(const QUrl &url, ProtocolHandler::RequestOptions o
}
}
if(this->current_identity.isValid() and this->current_identity.isHostFiltered(url)) {
auto answer = QMessageBox::question(
this,
"Kristall",
tr("Your client certificate has a host filter enabled and this site does not match the host filter.\r\nNew URL: %1\r\nHost Filter: %2\r\nDo you want to keep the certificate enabled?")
.arg(url.toString(QUrl::FullyEncoded))
.arg(this->current_identity.host_filter),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if(answer != QMessageBox::Yes) {
this->disableClientCertificate();
}
}
else if(not this->current_identity.isValid()) {
for(auto ident_ptr : global_identities.allIdentities())
{
if(ident_ptr->isAutomaticallyEnabledOn(url)) {
auto answer = QMessageBox::question(
this,
"Kristall",
tr("An automatic client certificate was detected for this site:\r\n%1\r\nDo you want to enable that certificate?")
.arg(ident_ptr->display_name),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if(answer != QMessageBox::Yes) {
break;
}
enableClientCertificate(*ident_ptr);
break;
}
}
}
if(this->current_identity.isValid()) {
if(not this->current_handler->enableClientCertificate(this->current_identity)) {
auto answer = QMessageBox::question(
this,
"Kristall",
tr("You requested a %1-URL with a client certificate, but these are not supported for this scheme. Continue?").arg(url.scheme())
);
if(answer != QMessageBox::Yes)
return false;
this->disableClientCertificate();
}
} else {
this->disableClientCertificate();
}
this->is_internal_location = (url.scheme() == "about");
this->current_location = url;
this->ui->url_bar->setText(url.toString(QUrl::FormattingOptions(QUrl::FullyEncoded)));
@ -858,6 +900,19 @@ bool BrowserTab::startRequest(const QUrl &url, ProtocolHandler::RequestOptions o
return this->current_handler->startRequest(url, options);
}
bool BrowserTab::enableClientCertificate(const CryptoIdentity &ident)
{
if (not ident.isValid())
{
QMessageBox::warning(this, "Kristall", "Failed to generate temporary crypto-identitiy");
this->disableClientCertificate();
return false;
}
this->current_identity = ident;
this->ui->enable_client_cert_button->setChecked(true);
return true;
}
void BrowserTab::disableClientCertificate()
{
for(auto & handler : this->protocol_handlers) {

View File

@ -129,6 +129,7 @@ private:
bool startRequest(QUrl const & url, ProtocolHandler::RequestOptions options);
bool enableClientCertificate(CryptoIdentity const & ident);
void disableClientCertificate();
public:

View File

@ -1,2 +1,31 @@
#include "cryptoidentity.hpp"
#include <QUrl>
#include <QRegExp>
#include <cassert>
bool CryptoIdentity::isHostFiltered(const QUrl &url) const
{
if(this->host_filter.isEmpty())
return false;
QString url_text = url.toString(QUrl::FullyEncoded);
QRegExp pattern { this->host_filter, Qt::CaseInsensitive, QRegExp::Wildcard };
return not pattern.exactMatch(url_text);
}
bool CryptoIdentity::isAutomaticallyEnabledOn(const QUrl &url) const
{
if(this->host_filter.isEmpty())
return false;
if(not this->auto_enable)
return false;
QString url_text = url.toString(QUrl::FullyEncoded);
QRegExp pattern { this->host_filter, Qt::CaseInsensitive, QRegExp::Wildcard };
return pattern.exactMatch(url_text);
}

View File

@ -35,6 +35,12 @@ struct CryptoIdentity
bool isValid() const {
return (not this->certificate.isNull()) and (not this->private_key.isNull());
}
//! returns true if a host does not match the filter criterion
bool isHostFiltered(QUrl const & url) const;
//! returns true when the identity should be enabled on url
bool isAutomaticallyEnabledOn(QUrl const & url) const;
};
#endif // CRYPTOIDENTITIY_HPP

View File

@ -244,6 +244,21 @@ bool IdentityCollection::deleteGroup(const QString &group_name)
return false;
}
QVector<const CryptoIdentity *> IdentityCollection::allIdentities() const
{
QVector<const CryptoIdentity *> identities;
for(auto const & group : this->root.children)
{
for(auto const & ident : group->children)
{
identities.append(&ident->as<IdentityNode>().identity);
}
}
return identities;
}
QModelIndex IdentityCollection::index(int row, int column, const QModelIndex &parent) const
{
if (not hasIndex(row, column, parent))

View File

@ -66,6 +66,9 @@ public:
bool canDeleteGroup(QString const & group_name);
bool deleteGroup(QString const & group_name);
//! Returns a list of non-mutable references to all contained identities
QVector<CryptoIdentity const *> allIdentities() const;
public:
// Header:
// QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;