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
34 changes: 34 additions & 0 deletions client/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ class DigidocConf final: public digidoc::XmlConfCurrent
enableLog(Settings::LIBDIGIDOCPP_DEBUG);
Settings::LIBDIGIDOCPP_DEBUG = false;
Settings::LIBDIGIDOCPP_DEBUG.registerCallback([this](const bool &value) { enableLog(value); });
#ifdef Q_OS_MACOS
if(Settings::PROXY_HOST.isSet() || Settings::PROXY_PORT.isSet()
|| Settings::PROXY_USER.isSet() || Settings::PROXY_PASS.isSet())
{
if(Application::setProxyCredentials({Settings::PROXY_HOST, Settings::PROXY_PORT,
Settings::PROXY_USER, Settings::PROXY_PASS}))
{
Settings::PROXY_HOST.clear();
Settings::PROXY_PORT.clear();
Settings::PROXY_USER.clear();
Settings::PROXY_PASS.clear();
}
}
#endif
#ifndef Q_OS_DARWIN
setTSLOnlineDigest(true);
#endif
Expand All @@ -123,24 +137,44 @@ class DigidocConf final: public digidoc::XmlConfCurrent

std::string proxyHost() const final
{
#ifdef Q_OS_MACOS
if(Settings::PROXY_CONFIG == Settings::ProxyManual)
if(const auto credentials = Application::proxyCredentials())
return credentials->host.toStdString();
#endif
return proxyConf(&QNetworkProxy::hostName,
Settings::PROXY_HOST, [this] { return digidoc::XmlConfCurrent::proxyHost(); });
}

std::string proxyPort() const final
{
#ifdef Q_OS_MACOS
if(Settings::PROXY_CONFIG == Settings::ProxyManual)
if(const auto credentials = Application::proxyCredentials())
return credentials->port.toStdString();
#endif
return proxyConf([](const QNetworkProxy &systemProxy) { return QString::number(systemProxy.port()); },
Settings::PROXY_PORT, [this] { return digidoc::XmlConfCurrent::proxyPort(); });
}

std::string proxyUser() const final
{
#ifdef Q_OS_MACOS
if(Settings::PROXY_CONFIG == Settings::ProxyManual)
if(const auto credentials = Application::proxyCredentials())
return credentials->user.toStdString();
#endif
return proxyConf(&QNetworkProxy::user,
Settings::PROXY_USER, [this] { return digidoc::XmlConfCurrent::proxyUser(); });
}

std::string proxyPass() const final
{
#ifdef Q_OS_MACOS
if(Settings::PROXY_CONFIG == Settings::ProxyManual)
if(const auto credentials = Application::proxyCredentials())
return credentials->password.toStdString();
#endif
return proxyConf(&QNetworkProxy::password,
Settings::PROXY_PASS, [this] { return digidoc::XmlConfCurrent::proxyPass(); });
}
Expand Down
19 changes: 19 additions & 0 deletions client/Application.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#pragma once

#include <optional>

#include <QtCore/QtGlobal>

#ifdef Q_OS_MAC
Expand Down Expand Up @@ -76,7 +78,24 @@ class Application final: public BaseApplication
static void showClient(QStringList params = {}, bool crypto = false, bool sign = false, bool newWindow = false);
static void updateTSLCache(const QDateTime &tslTime);
#if defined(Q_OS_MAC)
struct ProxyCredentials
{
ProxyCredentials(QString host, QString port, QString user, QString password);
ProxyCredentials(ProxyCredentials &&) noexcept;
~ProxyCredentials();

Q_DISABLE_COPY(ProxyCredentials)
ProxyCredentials &operator=(ProxyCredentials &&) = delete;

QString host;
QString port;
QString user;
QString password;
};

static QString groupContainerPath();
static std::optional<ProxyCredentials> proxyCredentials();
static bool setProxyCredentials(ProxyCredentials credentials);
#endif

private Q_SLOTS:
Expand Down
96 changes: 95 additions & 1 deletion client/Application_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,24 @@

#include "Application.h"

#include <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#include <Security/Security.h>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QtGui/QDesktopServices>

using namespace Qt::StringLiterals;

static NSMutableDictionary *proxyCredentialsQuery()
{
return [@{
(__bridge id)kSecClass: (__bridge id)kSecClassInternetPassword,
(__bridge id)kSecAttrProtocol: (__bridge id)kSecAttrProtocolHTTPProxy,
(__bridge id)kSecAttrSecurityDomain:
QStringLiteral("%1.proxy").arg(QGuiApplication::desktopFileName()).toNSString()
} mutableCopy];
}

static auto fetchPaths(NSPasteboard *pboard)
{
QStringList result;
Expand Down Expand Up @@ -116,3 +127,86 @@ - (void)openCrypto:(NSPasteboard *)pboard userData:(NSString *)data error:(NSStr
containerURLForSecurityApplicationGroupIdentifier:@"group.ee.ria.qdigidoc4.tsl"].path);
}

Application::ProxyCredentials::ProxyCredentials(
QString host, QString port, QString user, QString password)
: host(std::move(host))
, port(std::move(port))
, user(std::move(user))
, password(std::move(password))
{}

Application::ProxyCredentials::ProxyCredentials(ProxyCredentials &&) noexcept = default;

Application::ProxyCredentials::~ProxyCredentials()
{
password.fill(QChar{});
}

std::optional<Application::ProxyCredentials> Application::proxyCredentials()
{
NSMutableDictionary *query = proxyCredentialsQuery();
query[(__bridge id)kSecReturnAttributes] = @YES;
query[(__bridge id)kSecReturnData] = @YES;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;

CFTypeRef result = nullptr;
const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if(status == errSecItemNotFound)
return std::nullopt;
if(status != errSecSuccess)
{
qWarning() << "Failed to read the proxy credentials from Keychain:" << status;
return std::nullopt;
}

NSDictionary *item = CFBridgingRelease(result);
NSData *data = item[(__bridge id)kSecValueData];
const quint16 port = [item[(__bridge id)kSecAttrPort] unsignedShortValue];
return ProxyCredentials {
QString::fromNSString(item[(__bridge id)kSecAttrServer]),
port ? QString::number(port) : QString(),
QString::fromNSString(item[(__bridge id)kSecAttrAccount]),
QString::fromUtf8(static_cast<const char *>(data.bytes), qsizetype(data.length))
};
}

bool Application::setProxyCredentials(ProxyCredentials credentials)
{
NSMutableDictionary *query = proxyCredentialsQuery();
if(credentials.host.isEmpty() && credentials.port.isEmpty() &&
credentials.user.isEmpty() && credentials.password.isEmpty())
{
const OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if(status == errSecSuccess || status == errSecItemNotFound)
return true;
qWarning() << "Failed to remove the proxy credentials from Keychain:" << status;
return false;
}

QByteArray utf8 = credentials.password.toUtf8();
auto utf8Scope = qScopeGuard([&] { utf8.fill(0); });
NSData *data = [NSData dataWithBytesNoCopy:utf8.data()
length:NSUInteger(utf8.size()) freeWhenDone:NO];
NSDictionary *attributes = @{
(__bridge id)kSecAttrServer: credentials.host.toNSString(),
(__bridge id)kSecAttrPort: @(credentials.port.toUShort()),
(__bridge id)kSecAttrAccount: credentials.user.toNSString(),
(__bridge id)kSecValueData: data
};

// SecItemUpdate keeps the stored password when the new value is empty,
// recreate the item to clear it
OSStatus status = utf8.isEmpty() ? errSecItemNotFound :
SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
if(status == errSecItemNotFound)
{
SecItemDelete((__bridge CFDictionaryRef)query);
[query addEntriesFromDictionary:attributes];
status = SecItemAdd((__bridge CFDictionaryRef)query, nullptr);
}

if(status == errSecSuccess)
return true;
qWarning() << "Failed to store the proxy credentials in Keychain:" << status;
return false;
}
2 changes: 1 addition & 1 deletion client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ if( APPLE )
set_source_files_properties(${RESOURCE_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set_source_files_properties( Application_mac.mm dialogs/CertificateDetails_mac.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc" )
set_source_files_properties( LdapSearch.cpp PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations" )
target_link_libraries(${PROJECT_NAME} "-framework QuickLookUI" "-fobjc-arc")
target_link_libraries(${PROJECT_NAME} "-framework QuickLookUI" "-framework Security" "-fobjc-arc")
find_library(PKCS11_MODULE NAMES opensc-pkcs11.so HINTS /Library/OpenSC/lib)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND cp -a ${PKCS11_MODULE} $<TARGET_FILE_DIR:${PROJECT_NAME}>
Expand Down
102 changes: 75 additions & 27 deletions client/dialogs/SettingsDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@ using namespace Qt::StringLiterals;

#define qdigidoc4log QStringLiteral("%1/%2.log").arg(QDir::tempPath(), QApplication::applicationName())

namespace
{

void applyProxyConfig(int config, const QString &host, const QString &port,
const QString &user, const QString &pass)
{
switch(config)
{
case Settings::ProxyNone:
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy({});
break;
case Settings::ProxySystem:
QNetworkProxyFactory::setUseSystemConfiguration(true);
break;
default:
QNetworkProxyFactory::setUseSystemConfiguration(false);
// QAuthenticator encodes Basic credentials with toLatin1() while proxies and
// libdigidocpp's Connect::sendProxyAuth() send them as UTF-8. Pre-encode the
// credentials so that toLatin1() restores the UTF-8 bytes; the conversion is
// a no-op for US-ASCII credentials. Only Basic is usable anyway, libdigidocpp
// implements no other proxy authentication scheme.
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy,
host, port.toUShort(),
QString::fromLatin1(user.toUtf8()), QString::fromLatin1(pass.toUtf8())));
break;
}
}

}

SettingsDialog::SettingsDialog(int page, QWidget *parent)
: QDialog(parent)
, ui(new Ui::SettingsDialog)
Expand Down Expand Up @@ -263,22 +294,33 @@ SettingsDialog::SettingsDialog(int page, QWidget *parent)
updateCDoc2Cert(QSslCertificate(QByteArray::fromBase64(Settings::CDOC2_GET_CERT), QSsl::Der));

// pageProxy
#ifdef Q_OS_MACOS
connect(this, &SettingsDialog::accepted, this, &SettingsDialog::saveProxy);
connect(this, &SettingsDialog::rejected, this, [] { loadProxy(digidoc::Conf::instance()); });
#else
connect(this, &SettingsDialog::finished, this, &SettingsDialog::saveProxy);
#endif
ui->proxyGroup->setId(ui->rdProxyNone, Settings::ProxyNone);
ui->proxyGroup->setId(ui->rdProxySystem, Settings::ProxySystem);
ui->proxyGroup->setId(ui->rdProxyManual, Settings::ProxyManual);
ui->wgtProxyManual->hide();
connect(ui->rdProxyManual, &QRadioButton::toggled, ui->wgtProxyManual, &QWidget::setVisible);
ui->proxyGroup->button(Settings::PROXY_CONFIG)->setChecked(true);
#ifdef Q_OS_MACOS
ui->txtProxyHost->setText(Settings::PROXY_HOST);
ui->txtProxyPort->setText(Settings::PROXY_PORT);
ui->txtProxyUsername->setText(Settings::PROXY_USER);
ui->txtProxyPassword->setText(Settings::PROXY_PASS);
connect(ui->txtProxyHost, &QLineEdit::textChanged, this, Settings::PROXY_HOST);
connect(ui->txtProxyPort, &QLineEdit::textChanged, this, Settings::PROXY_PORT);
connect(ui->txtProxyUsername, &QLineEdit::textChanged, this, Settings::PROXY_USER);
connect(ui->txtProxyPassword, &QLineEdit::textChanged, this, Settings::PROXY_PASS);
if(const auto credentials = Application::proxyCredentials())
{
ui->txtProxyHost->setText(credentials->host);
ui->txtProxyPort->setText(credentials->port);
ui->txtProxyUsername->setText(credentials->user);
ui->txtProxyPassword->setText(credentials->password);
}
else
{
ui->txtProxyHost->setText(Settings::PROXY_HOST);
ui->txtProxyPort->setText(Settings::PROXY_PORT);
ui->txtProxyUsername->setText(Settings::PROXY_USER);
ui->txtProxyPassword->setText(Settings::PROXY_PASS);
}
#else
if(auto *i = digidoc::XmlConfCurrent::instance())
{
Expand Down Expand Up @@ -431,7 +473,11 @@ QString SettingsDialog::certInfo(const SslCertificate &c)
void SettingsDialog::checkConnection()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
#ifdef Q_OS_MACOS
applyProxy();
#else
saveProxy();
#endif
if(CheckConnection connection; !connection.check())
{
Application::restoreOverrideCursor();
Expand Down Expand Up @@ -508,7 +554,16 @@ void SettingsDialog::selectLanguage()
void SettingsDialog::saveProxy()
{
Settings::PROXY_CONFIG = ui->proxyGroup->checkedId();
#ifndef Q_OS_MACOS
#ifdef Q_OS_MACOS
if(Application::setProxyCredentials({ui->txtProxyHost->text(), ui->txtProxyPort->text(),
ui->txtProxyUsername->text(), ui->txtProxyPassword->text()}))
{
Settings::PROXY_HOST.clear();
Settings::PROXY_PORT.clear();
Settings::PROXY_USER.clear();
Settings::PROXY_PASS.clear();
}
#else
if(auto *i = digidoc::XmlConfCurrent::instance())
{
i->setProxyHost(ui->txtProxyHost->text().toStdString());
Expand All @@ -520,26 +575,19 @@ void SettingsDialog::saveProxy()
loadProxy(digidoc::Conf::instance());
}

void SettingsDialog::applyProxy() const
{
applyProxyConfig(ui->proxyGroup->checkedId(), ui->txtProxyHost->text(),
ui->txtProxyPort->text(), ui->txtProxyUsername->text(), ui->txtProxyPassword->text());
}

void SettingsDialog::loadProxy( const digidoc::Conf *conf )
{
switch(Settings::PROXY_CONFIG)
{
case Settings::ProxyNone:
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy({});
break;
case Settings::ProxySystem:
QNetworkProxyFactory::setUseSystemConfiguration(true);
break;
default:
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy,
QString::fromStdString(conf->proxyHost()),
QString::fromStdString(conf->proxyPort()).toUShort(),
QString::fromStdString(conf->proxyUser()),
QString::fromStdString(conf->proxyPass())));
break;
}
applyProxyConfig(Settings::PROXY_CONFIG,
QString::fromStdString(conf->proxyHost()),
QString::fromStdString(conf->proxyPort()),
QString::fromStdString(conf->proxyUser()),
QString::fromStdString(conf->proxyPass()));
}

void SettingsDialog::updateDiagnostics()
Expand Down
1 change: 1 addition & 0 deletions client/dialogs/SettingsDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class SettingsDialog final: public QDialog

private:
void checkConnection();
void applyProxy() const;
void retranslate(const QString& lang);
void saveFile(const QString &name, const QString &path);
void saveFile(const QString &name, const QByteArray &content);
Expand Down
Loading