diff --git a/CMakeLists.txt b/CMakeLists.txt
index 869fd88a0..50c446bd0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,6 +23,7 @@ include_directories( ${LIBDIGIDOCPP_INCLUDE_DIR} )
set_env( TSL_URL "https://ec.europa.eu/tools/lotl/eu-lotl.xml" CACHE STRING "TSL trust list primary URL" )
set_env( TSL_INCLUDE "EE" CACHE STRING "TSL list include in binary" )
set_env( MOBILEID_URL "https://tsp.demo.sk.ee/mid-api" CACHE STRING "URL for Mobile-ID" )
+set_env( SMARTID_URL "https://sid.demo.sk.ee/smart-id-rp/v1" CACHE STRING "URL for Smart-ID" )
if(APPLE)
set(BUNDLE_NAME qdigidoc4)
diff --git a/client/Application.cpp b/client/Application.cpp
index ee3604349..5012f3db6 100644
--- a/client/Application.cpp
+++ b/client/Application.cpp
@@ -547,6 +547,9 @@ void Application::diagnostics(QTextStream &s)
<< "
SIVA_URL: " << confValue(SiVaUrl).toString()
#ifdef MOBILEID_URL
<< "
MOBILEID_URL: " << MOBILEID_URL
+#endif
+#ifdef SMARTID_URL
+ << "
SMARTID_URL: " << SMARTID_URL
#endif
<< "
" << tr("TSL signing certs") << ":";
for(const QSslCertificate &cert: confValue(TSLCerts).value>())
@@ -1020,6 +1023,9 @@ void DdCliApplication::diagnostics(QTextStream &s) const
#ifdef MOBILEID_URL
s << "
MOBILEID_URL: " << MOBILEID_URL;
+#endif
+#ifdef SMARTID_URL
+ s << "
SMARTID_URL: " << SMARTID_URL;
#endif
s << "
TSL_URL: " << Application::confValue(Application::TSLUrl).toString();
s << "
" << "TSL signing certs:";
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index a43bff83c..106a7e09c 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -28,6 +28,7 @@ qt5_wrap_ui( SOURCES
dialogs/RoleAddressDialog.ui
dialogs/SettingsDialog.ui
dialogs/SignatureDialog.ui
+ dialogs/SmartIDDialog.ui
dialogs/WaitDialog.ui
dialogs/WarningDialog.ui
widgets/Accordion.ui
@@ -84,6 +85,8 @@ add_executable( ${PROGNAME} WIN32 MACOSX_BUNDLE
dialogs/PinUnblock.cpp
dialogs/RoleAddressDialog.cpp
dialogs/SettingsDialog.cpp
+ dialogs/SmartIDDialog.cpp
+ dialogs/SmartIDProgress.cpp
dialogs/SignatureDialog.cpp
dialogs/WaitDialog.cpp
dialogs/WarningDialog.cpp
@@ -123,7 +126,7 @@ target_link_libraries( ${PROGNAME}
${LDAP_LIBRARIES}
)
-target_compile_definitions(${PROGNAME} PRIVATE MOBILEID_URL="${MOBILEID_URL}")
+target_compile_definitions(${PROGNAME} PRIVATE MOBILEID_URL="${MOBILEID_URL}" SMARTID_URL="${SMARTID_URL}" )
if( APPLE )
target_sources( ${PROGNAME} PRIVATE Application_mac.mm dialogs/CertificateDetails_mac.mm )
diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp
index 62a3ae995..b5b81615e 100644
--- a/client/MainWindow.cpp
+++ b/client/MainWindow.cpp
@@ -40,6 +40,7 @@
#include "dialogs/MobileProgress.h"
#include "dialogs/RoleAddressDialog.h"
#include "dialogs/SettingsDialog.h"
+#include "dialogs/SmartIDProgress.h"
#include "dialogs/WaitDialog.h"
#include "dialogs/WarningDialog.h"
#include "widgets/WarningList.h"
@@ -499,6 +500,14 @@ void MainWindow::onSignAction(int action, const QString &info1, const QString &i
return digiDoc->sign(city, state, zip, country, role, &m);
});
break;
+ case SignatureSmartID:
+ sign([this, info1, info2](const QString &city, const QString &state, const QString &zip, const QString &country, const QString &role) {
+ SmartIDProgress s(this);
+ if(!s.init(info1, info2))
+ return false;
+ return digiDoc->sign(city, state, zip, country, role, &s);
+ });
+ break;
case ClearSignatureWarning:
ui->signature->warningIcon(false);
warnings->closeWarnings(SignDetails);
diff --git a/client/common_enums.h b/client/common_enums.h
index 45dc2e36e..e54e46b74 100644
--- a/client/common_enums.h
+++ b/client/common_enums.h
@@ -63,6 +63,7 @@ enum Actions {
SignatureAdd,
SignatureMobile,
+ SignatureSmartID,
SignatureToken,
SignatureRemove,
SignatureWarning,
diff --git a/client/dialogs/MobileProgress.cpp b/client/dialogs/MobileProgress.cpp
index c19f9c40c..8b72555e3 100644
--- a/client/dialogs/MobileProgress.cpp
+++ b/client/dialogs/MobileProgress.cpp
@@ -161,7 +161,7 @@ MobileProgress::MobileProgress(QWidget *parent)
returnError(tr("Connecting to SK server failed! Please check your internet connection."));
return;
case QNetworkReply::ConnectionRefusedError:
- returnError(tr("Mobile-ID service has encountered technical errors. Please try again later."));
+ returnError(tr("%1 service has encountered technical errors. Please try again later.").arg(tr("Mobile-ID")));
return;
case QNetworkReply::SslHandshakeFailedError:
d->l.exit(Private::GeneralError);
diff --git a/client/dialogs/SmartIDDialog.cpp b/client/dialogs/SmartIDDialog.cpp
new file mode 100644
index 000000000..e3725e3ef
--- /dev/null
+++ b/client/dialogs/SmartIDDialog.cpp
@@ -0,0 +1,47 @@
+#include "SmartIDDialog.h"
+#include "ui_SmartIDDialog.h"
+
+#include "Settings.h"
+#include "Styles.h"
+#include "effects/Overlay.h"
+
+SmartIDDialog::SmartIDDialog(QWidget *parent)
+ : QDialog(parent)
+ , ui(new Ui::SmartIDDialog)
+{
+ Overlay *overlay = new Overlay(parent->topLevelWidget());
+ overlay->show();
+ connect(this, &SmartIDDialog::destroyed, overlay, &Overlay::deleteLater);
+
+ ui->setupUi(this);
+ setWindowFlags( Qt::Dialog | Qt::FramelessWindowHint );
+ setWindowModality( Qt::ApplicationModal );
+ setFixedSize( size() );
+ QFont condensed12 = Styles::font(Styles::Condensed, 12);
+ QFont condensed14 = Styles::font(Styles::Condensed, 14);
+ ui->labelPhone->setFont(condensed12);
+ ui->idCode->setFont(condensed12);
+ ui->sign->setFont(condensed14);
+ ui->cancel->setFont(condensed14);
+ ui->idCode->setText(Settings(qApp->applicationName()).value(QStringLiteral("SmartID")).toString());
+ connect(ui->sign, &QPushButton::clicked, this, &QDialog::accept);
+ connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject);
+ connect(ui->idCode, &QLineEdit::textEdited, this, [](const QString &text){
+ Settings(qApp->applicationName()).setValueEx(QStringLiteral("SmartID"), text, QString());
+ });
+}
+
+SmartIDDialog::~SmartIDDialog()
+{
+ delete ui;
+}
+
+QString SmartIDDialog::country() const
+{
+ return QStringLiteral("EE");
+}
+
+QString SmartIDDialog::idCode() const
+{
+ return ui->idCode->text();
+}
diff --git a/client/dialogs/SmartIDDialog.h b/client/dialogs/SmartIDDialog.h
new file mode 100644
index 000000000..97683ada2
--- /dev/null
+++ b/client/dialogs/SmartIDDialog.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+
+namespace Ui { class SmartIDDialog; }
+
+class SmartIDDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SmartIDDialog(QWidget *parent = nullptr);
+ ~SmartIDDialog() final;
+
+ QString country() const;
+ QString idCode() const;
+
+private:
+ Ui::SmartIDDialog *ui;
+};
diff --git a/client/dialogs/SmartIDDialog.ui b/client/dialogs/SmartIDDialog.ui
new file mode 100644
index 000000000..d0acd8032
--- /dev/null
+++ b/client/dialogs/SmartIDDialog.ui
@@ -0,0 +1,217 @@
+
+
+ SmartIDDialog
+
+
+
+ 0
+ 0
+ 380
+ 282
+
+
+
+ Smart-ID
+
+
+ QDialog {
+border-radius: 2px;
+background-color: #FFFFFF;
+}
+QLineEdit, QComboBox {
+padding: 0px 10px;
+border: 1px solid #DEE4E9;
+border-radius: 2px;
+}
+
+
+
+ 0
+
+
+ 50
+
+
+ 30
+
+
+ 50
+
+
+ 30
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 67
+
+
+
+
+ -
+
+
+
+ Roboto Condensed
+ 12
+
+
+
+ color: #353739;
+
+
+ Personal code
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 34
+
+
+
-
+
+ EE
+
+
+ -
+
+ LT
+
+
+ -
+
+ LV
+
+
+
+
+ -
+
+
+
+ 0
+ 34
+
+
+
+ 47101010033
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 66
+
+
+
+
+ -
+
+
+ 40
+
+
-
+
+
+ true
+
+
+
+ 120
+ 30
+
+
+
+
+ 14
+
+
+
+ PointingHandCursor
+
+
+ QPushButton {
+ border-radius: 2px;
+ border: none;
+ color: #ffffff;
+ background-color: #981E32;
+}
+QPushButton:pressed {
+ background-color: #F24A66;
+}
+QPushButton:hover:!pressed {
+ background-color: #CD2541;
+}
+
+
+ CANCEL
+
+
+ false
+
+
+
+ -
+
+
+
+ 120
+ 30
+
+
+
+
+ 14
+
+
+
+ QPushButton {
+ border-radius: 2px;
+ border: none;
+ color: #ffffff;
+ background-color: #006EB5;
+}
+QPushButton:pressed {
+ background-color: #41B6E6;
+}
+QPushButton:hover:!pressed {
+ background-color: #008DCF;
+}
+QPushButton:disabled {
+ background-color: #BEDBED;
+}
+
+
+ SIGN
+
+
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/client/dialogs/SmartIDProgress.cpp b/client/dialogs/SmartIDProgress.cpp
new file mode 100644
index 000000000..1a9427627
--- /dev/null
+++ b/client/dialogs/SmartIDProgress.cpp
@@ -0,0 +1,345 @@
+/*
+ * QDigiDocClient
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "SmartIDProgress.h"
+#include "ui_MobileProgress.h"
+
+#include "Styles.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef Q_OS_WIN
+#include
+#include
+#endif
+
+#include
+#include
+
+#include
+
+Q_LOGGING_CATEGORY(SIDLog,"RIA.SmartID")
+
+class SmartIDProgress::Private: public QDialog, public Ui::MobileProgress
+{
+ Q_OBJECT
+public:
+ QString URL() { return UUID.isNull() ? PROXYURL : SKURL; }
+ enum {
+ GeneralError = -1,
+ AccountNotFound = -2,
+ SessionNotNound = -3,
+ };
+ using QDialog::QDialog;
+ QTimeLine *statusTimer = nullptr;
+ QNetworkAccessManager *manager = nullptr;
+ QNetworkRequest req;
+ QString documentNumber, sessionID, lastError;
+ digidoc::X509Cert cert;
+ std::vector signature;
+ QEventLoop l;
+#ifdef CONFIG_URL
+ QString PROXYURL = Configuration::instance().object().value(QStringLiteral("SID-PROXY-URL")).toString(QStringLiteral(SMARTID_URL));
+ QString SKURL = Configuration::instance().object().value(QStringLiteral("SID-SK-URL")).toString(QStringLiteral(SMARTID_URL));
+#else
+ QString PROXYURL = Settings(qApp->applicationName()).value(QStringLiteral("SID-PROXY-URL"), QStringLiteral(SMARTID_URL)).toString();
+ QString SKURL = Settings(qApp->applicationName()).value(QStringLiteral("SID-SK-URL"), QStringLiteral(SMARTID_URL)).toString();
+#endif
+ QString NAME = Settings(qApp->applicationName()).value(QStringLiteral("SIDNAME"), QStringLiteral("DigiDoc4")).toString();
+ QUuid UUID = Settings(qApp->applicationName()).value(QStringLiteral("SIDUUID")).toUuid();
+#ifdef Q_OS_WIN
+ QWinTaskbarButton *taskbar = nullptr;
+#endif
+};
+
+
+
+SmartIDProgress::SmartIDProgress(QWidget *parent)
+ : d(new Private(parent))
+{
+ d->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint);
+ d->setWindowModality(Qt::ApplicationModal);
+ d->setupUi(d);
+ d->code->setBuddy(d->signProgressBar);
+ d->code->setFont(Styles::font(Styles::Regular, 20, QFont::DemiBold));
+ d->labelError->setFont(Styles::font(Styles::Regular, 14));
+ d->signProgressBar->setFont(d->labelError->font());
+ d->cancel->setFont(Styles::font(Styles::Condensed, 14));
+ QObject::connect(d->cancel, &QPushButton::clicked, d, &QDialog::reject);
+ QObject::connect(d->cancel, &QPushButton::clicked, [=] { d->l.exit(QDialog::Rejected); });
+
+ d->statusTimer = new QTimeLine(d->signProgressBar->maximum() * 1000, d);
+ d->statusTimer->setCurveShape(QTimeLine::LinearCurve);
+ d->statusTimer->setFrameRange(d->signProgressBar->minimum(), d->signProgressBar->maximum());
+ QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->signProgressBar, &QProgressBar::setValue);
+ QObject::connect(d->statusTimer, &QTimeLine::finished, d, &QDialog::reject);
+#ifdef Q_OS_WIN
+ d->taskbar = new QWinTaskbarButton(d);
+ d->taskbar->setWindow(parent->windowHandle());
+ d->taskbar->progress()->setRange(d->signProgressBar->minimum(), d->signProgressBar->maximum());
+ QObject::connect(d->statusTimer, &QTimeLine::frameChanged, d->taskbar->progress(), &QWinTaskbarProgress::setValue);
+#endif
+
+ QSslConfiguration ssl = QSslConfiguration::defaultConfiguration();
+#ifdef CONFIG_URL
+ ssl.setCaCertificates({});
+ QList trusted;
+ for(const QJsonValue &cert: Configuration::instance().object().value(QStringLiteral("CERT-BUNDLE")).toArray())
+ trusted << QSslCertificate(QByteArray::fromBase64(cert.toString().toLatin1()), QSsl::Der);
+#endif
+ d->req.setSslConfiguration(ssl);
+ d->req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ d->req.setRawHeader("User-Agent", QStringLiteral("%1/%2 (%3)")
+ .arg(qApp->applicationName(), qApp->applicationVersion(), Common::applicationOs()).toUtf8());
+ d->manager = new QNetworkAccessManager(d);
+ QObject::connect(d->manager, &QNetworkAccessManager::sslErrors, [=](QNetworkReply *reply, const QList &err) {
+ QStringList msg;
+ QList ignore;
+ for(const QSslError &e: err)
+ {
+ switch(e.error())
+ {
+ case QSslError::UnableToGetLocalIssuerCertificate:
+ case QSslError::CertificateUntrusted:
+ case QSslError::SelfSignedCertificateInChain:
+ if(trusted.contains(reply->sslConfiguration().peerCertificate())) {
+ ignore << e;
+ break;
+ }
+ default:
+ qCWarning(SIDLog) << "SSL Error:" << e.error() << e.certificate().subjectInfo(QSslCertificate::CommonName);
+ msg << e.errorString();
+ break;
+ }
+ }
+ reply->ignoreSslErrors(ignore);
+ if(!msg.empty())
+ {
+ d->labelError->setText(tr("SSL handshake failed. Check the proxy settings of your computer or software upgrades."));
+ d->code->hide();
+ }
+ });
+ QNetworkAccessManager::connect(d->manager, &QNetworkAccessManager::finished, d, [&](QNetworkReply *reply){
+ QScopedPointer scope(reply);
+ auto returnError = [=](const QString &err) {
+ qCWarning(SIDLog) << (d->lastError = err);
+ d->l.exit(Private::GeneralError);
+ };
+
+ switch(reply->error())
+ {
+ case QNetworkReply::NoError:
+ break;
+ case QNetworkReply::ContentNotFoundError:
+ d->l.exit(d->sessionID.isEmpty() ? Private::AccountNotFound : Private::SessionNotNound);
+ return;
+ case QNetworkReply::HostNotFoundError:
+ returnError(tr("Connecting to SK server failed! Please check your internet connection."));
+ return;
+ case QNetworkReply::ConnectionRefusedError:
+ returnError(tr("%1 service has encountered technical errors. Please try again later.").arg(tr("Smart-ID")));
+ return;
+ case QNetworkReply::SslHandshakeFailedError:
+ d->l.exit(Private::GeneralError);
+ return;
+ case QNetworkReply::ProtocolInvalidOperationError:
+ qCWarning(SIDLog) << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ returnError(reply->readAll());
+ return;
+ default:
+ qCWarning(SIDLog) << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << "Error :" << reply->error();
+ if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 429)
+ returnError(tr("The limit for digital signatures per month has been reached for this IP address. "));
+ else
+ returnError(tr("Failed to send request ") + reply->errorString());
+ return;
+ }
+ static const QStringList contentType{"application/json", "application/json;charset=UTF-8"};
+ if(!contentType.contains(reply->header(QNetworkRequest::ContentTypeHeader).toString()))
+ {
+ returnError(tr("Invalid content type header ") + reply->header(QNetworkRequest::ContentTypeHeader).toString());
+ return;
+ }
+
+ QByteArray data = reply->readAll();
+ reply->deleteLater();
+ qCDebug(SIDLog).noquote() << data;
+ QJsonObject result = QJsonDocument::fromJson(data, nullptr).object();
+ if(result.isEmpty())
+ {
+ returnError(tr("Failed to parse JSON content"));
+ return;
+ }
+ if(d->sessionID.isEmpty())
+ d->sessionID = result.value(QStringLiteral("sessionID")).toString();
+ else if(result.value(QStringLiteral("state")).toString() != QStringLiteral("RUNNING"))
+ {
+ QString endResult = result.value(QStringLiteral("result")).toObject().value(QStringLiteral("endResult")).toString();
+ if(endResult == QStringLiteral("USER_REFUSED"))
+ d->l.exit(QDialog::Rejected);
+ else if(endResult != QStringLiteral("OK"))
+ returnError(tr("Service result: ") + endResult);
+ else if(d->documentNumber.isEmpty())
+ d->documentNumber = result.value(QStringLiteral("result")).toObject().value(QStringLiteral("documentNumber")).toString();
+ if(result.contains(QStringLiteral("signature")))
+ {
+ QByteArray b64 = QByteArray::fromBase64(
+ result.value(QStringLiteral("signature")).toObject().value(QStringLiteral("value")).toString().toUtf8());
+ d->signature.assign(b64.cbegin(), b64.cend());
+ d->l.exit(QDialog::Accepted);
+ }
+ else if(result.contains(QStringLiteral("cert")))
+ {
+ try {
+ QByteArray b64 = QByteArray::fromBase64(
+ result.value(QStringLiteral("cert")).toObject().value(QStringLiteral("value")).toString().toUtf8());
+ d->cert = digidoc::X509Cert((const unsigned char*)b64.constData(), size_t(b64.size()), digidoc::X509Cert::Der);
+ d->l.exit(QDialog::Accepted);
+ } catch(const digidoc::Exception &e) {
+ returnError(tr("Failed to parse certificate: ") + QString::fromStdString(e.msg()));
+ }
+ }
+ return;
+ }
+ d->req.setUrl(QUrl(QStringLiteral("%1/session/%2?timeoutMs=10000").arg(d->URL(), d->sessionID)));
+ qCDebug(SIDLog).noquote() << d->req.url();
+ d->manager->get(d->req);
+ });
+}
+
+SmartIDProgress::~SmartIDProgress()
+{
+ d->deleteLater();
+}
+
+digidoc::X509Cert SmartIDProgress::cert() const
+{
+ return d->cert;
+}
+
+void SmartIDProgress::endProgress(const QString &msg)
+{
+ d->labelError->setText(msg);
+ d->signProgressBar->hide();
+ stop();
+}
+
+bool SmartIDProgress::init(const QString &country, const QString &idCode)
+{
+ d->sessionID.clear();
+ d->lastError.clear();
+ QByteArray data = QJsonDocument(QJsonObject::fromVariantHash(QVariantHash{
+ {"relyingPartyUUID", d->UUID.toString().remove('{').remove('}')}, // FIXME Qt 5.11: toString(QUuid::WithoutBraces)},
+ {"relyingPartyName", d->NAME},
+ {"certificateLevel", "QUALIFIED"}
+ })).toJson();
+ d->req.setUrl(QUrl(QStringLiteral("%1/certificatechoice/pno/%2/%3").arg(d->URL(), country, idCode)));
+ qCDebug(SIDLog).noquote() << d->req.url() << data;
+ d->manager->post(d->req, data);
+ switch(d->l.exec())
+ {
+ case QDialog::Accepted: return true;
+ case QDialog::Rejected: return false;
+ case Private::AccountNotFound:
+ endProgress(tr("Account not found"));
+ return false;
+ default:
+ if(d->lastError.isEmpty())
+ endProgress(tr("Failed to sign container"));
+ else
+ endProgress(d->lastError);
+ return false;
+ }
+}
+
+std::vector SmartIDProgress::sign(const std::string &method, const std::vector &digest) const
+{
+ d->statusTimer->stop();
+ d->sessionID.clear();
+ d->lastError.clear();
+
+ QString digestMethod;
+ if(method == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
+ digestMethod = QStringLiteral("SHA256");
+ else if(method == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384")
+ digestMethod = QStringLiteral("SHA384");
+ else if(method == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512")
+ digestMethod = QStringLiteral("SHA512");
+ else
+ throw digidoc::Exception(__FILE__, __LINE__, "Unsupported digest method");
+
+ QByteArray codeDiest = QCryptographicHash::hash(QByteArray::fromRawData((const char*)digest.data(), int(digest.size())), QCryptographicHash::Sha256);
+ int code = codeDiest.right(2).toHex().toUInt(nullptr, 16) % 10000;
+ d->code->setText(tr("Make sure control code matches with one in phone screen\n"
+ "and enter Mobile-ID PIN2-code.\nControl code: %1")
+ .arg(code, 4, 10, QChar('0')));
+
+ QByteArray data = QJsonDocument(QJsonObject::fromVariantHash({
+ {"relyingPartyUUID", d->UUID.toString().remove('{').remove('}')}, // FIXME Qt 5.11: toString(QUuid::WithoutBraces)},
+ {"relyingPartyName", d->NAME},
+ {"certificateLevel", "QUALIFIED"},
+ {"hash", QByteArray::fromRawData((const char*)digest.data(), int(digest.size())).toBase64()},
+ {"hashType", digestMethod},
+ {"requestProperties", QJsonObject::fromVariantHash({{"vcChoice", true}})},
+ {"displayText", "Sign document"}
+ })).toJson();
+ d->req.setUrl(QUrl(QStringLiteral("%1/signature/document/%2").arg(d->URL(), d->documentNumber)));
+ qCDebug(SIDLog).noquote() << d->req.url() << data;
+ d->manager->post(d->req, data);
+ d->statusTimer->start();
+ d->adjustSize();
+ d->show();
+ switch(d->l.exec())
+ {
+ case QDialog::Accepted: return d->signature;
+ case QDialog::Rejected:
+ {
+ digidoc::Exception e(__FILE__, __LINE__, "Signing canceled");
+ e.setCode(digidoc::Exception::PINCanceled);
+ throw e;
+ }
+ default:
+ if(d->lastError.isEmpty())
+ throw digidoc::Exception(__FILE__, __LINE__, "Failed to sign container");
+ throw digidoc::Exception(__FILE__, __LINE__, d->lastError.toUtf8().constData());
+ }
+}
+
+void SmartIDProgress::stop()
+{
+ d->statusTimer->stop();
+#ifdef Q_OS_WIN
+ d->taskbar->progress()->stop();
+ d->taskbar->progress()->hide();
+#endif
+}
+
+#include "SmartIDProgress.moc"
diff --git a/client/dialogs/SmartIDProgress.h b/client/dialogs/SmartIDProgress.h
new file mode 100644
index 000000000..e1edf6846
--- /dev/null
+++ b/client/dialogs/SmartIDProgress.h
@@ -0,0 +1,44 @@
+/*
+ * QDigiDocClient
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#pragma once
+
+#include
+
+#include
+
+class QWidget;
+
+class SmartIDProgress: public digidoc::Signer
+{
+ Q_DECLARE_TR_FUNCTIONS(MobileProgress)
+public:
+ SmartIDProgress(QWidget *parent = nullptr);
+ ~SmartIDProgress() final;
+ digidoc::X509Cert cert() const final;
+ bool init(const QString &country, const QString &idCode);
+ std::vector sign(const std::string &method, const std::vector &digest) const final;
+
+private:
+ void endProgress(const QString &msg);
+ void stop();
+
+ class Private;
+ Private *d;
+};
diff --git a/client/translations/en.ts b/client/translations/en.ts
index cc1b04c9c..df37d8561 100755
--- a/client/translations/en.ts
+++ b/client/translations/en.ts
@@ -1207,6 +1207,11 @@ LDAP server is unavailable.
SIGN WITH
MOBIIL-ID
+
+
+
+ SIGN WITH
+SMARTID
@@ -1655,8 +1660,8 @@ Control code: %1
Cancel
-
- Mobile-ID service has encountered technical errors. Please try again later.
+
+ %1 service has encountered technical errors. Please try again later.
@@ -1686,6 +1691,18 @@ Control code: %1
The limit for digital signatures per month has been reached for this IP address. <a href="https://www.id.ee/index.php?id=39023"Additional information</a>
+
+
+ Mobile-ID
+
+
+
+ Smart-ID
+
+
+
+
+
NoCardInfo
@@ -2885,6 +2902,21 @@ Additional licenses and components
To view signature details press enter or space
+
+ SmartIDDialog
+
+
+ SIGN
+
+
+
+ CANCEL
+
+
+
+ PERSONAL CODE
+
+
SslCertificate
diff --git a/client/translations/et.ts b/client/translations/et.ts
index 33a239c9c..1b108d171 100755
--- a/client/translations/et.ts
+++ b/client/translations/et.ts
@@ -1207,6 +1207,11 @@ LDAP serveriga ei saa ühendust.
ALLKIRJASTA
MOBIIL-ID’GA
+
+
+
+ ALLKIRJASTA
+SMARTID’GA
@@ -1655,8 +1660,8 @@ Kontrollkood: %1
Katkesta
-
- Mobiil-ID teenuses esinevad tehnilised tõrked. Palun proovige mõne aja pärast uuesti.
+
+ %1 teenuses esinevad tehnilised tõrked. Palun proovige mõne aja pärast uuesti.
@@ -1686,6 +1691,18 @@ Kontrollkood: %1
Sinu IP-aadressi tasuta allkirjade kuulimiit on ületatud. <a href="https://id.ee/index.php?id=39021">Loe täpsemalt siit</a>
+
+
+ Mobiil-ID
+
+
+
+ Smart-ID
+
+
+
+
+
NoCardInfo
@@ -2885,6 +2902,21 @@ Täiendavad litsentsid ja komponendid
Allkirja detailide vaatamiseks vajuta space või enter
+
+ SmartIDDialog
+
+
+ ALLKIRJASTA
+
+
+
+ KATKESTA
+
+
+
+ ISIKUKOOD
+
+
SslCertificate
diff --git a/client/translations/ru.ts b/client/translations/ru.ts
index f20febb39..8fc7877d3 100755
--- a/client/translations/ru.ts
+++ b/client/translations/ru.ts
@@ -1209,6 +1209,11 @@ LDAP сервер недоступен.
ПОДПИСАТЬ С
MOBIIL-ID
+
+
+
+ ПОДПИСАТЬ С
+SMARTID
@@ -1658,8 +1663,8 @@ Kонтрольный код: %1
Отмена
-
- Технический сбой услуги Mobiil-ID. Пожалуйста, попробуйте позже.
+
+ Технический сбой услуги %1. Пожалуйста, попробуйте позже.
@@ -1689,6 +1694,18 @@ Kонтрольный код: %1
Предел для цифровых подписей в месяц был достигнут для этого IP-адреса. <a href="https://www.id.ee/index.php?id=39025">Дополнительная информация</a>
+
+
+ Mobiil-ID
+
+
+
+ Smart-ID
+
+
+
+
+
NoCardInfo
@@ -2888,6 +2905,21 @@ Additional licenses and components
Для просмотра деталей подписи нажмите пробел или enter
+
+ SmartIDDialog
+
+
+ ПОДПИСАТЬ
+
+
+
+ ОТМЕНА
+
+
+
+ ЛИЧНЫЙ КОД
+
+
SslCertificate
diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp
index 9f63835ff..544636d36 100644
--- a/client/widgets/ContainerPage.cpp
+++ b/client/widgets/ContainerPage.cpp
@@ -26,6 +26,7 @@
#include "crypto/CryptoDoc.h"
#include "dialogs/AddRecipients.h"
#include "dialogs/MobileDialog.h"
+#include "dialogs/SmartIDDialog.h"
#include "dialogs/WarningDialog.h"
#include "widgets/AddressItem.h"
#include "widgets/SignatureItem.h"
@@ -126,6 +127,7 @@ bool ContainerPage::checkAction(int code, const QString& selectedCard, const QSt
case SignatureAdd:
case SignatureToken:
case SignatureMobile:
+ case SignatureSmartID:
if(ui->rightPane->hasItem(
[selectedCard, selectedMobile, code](Item* const item) -> bool
{
@@ -211,6 +213,13 @@ void ContainerPage::forward(int code)
window()->setWindowTitle(tr("DigiDoc4 client"));
emit action(code);
break;
+ case SignatureSmartID:
+ {
+ SmartIDDialog dlg(qApp->activeWindow());
+ if(dlg.exec() == QDialog::Accepted)
+ emit action(SignatureSmartID, dlg.country(), dlg.idCode());
+ break;
+ }
default:
if(checkAction(code, cardInReader, mobileCode))
emit action(code);
@@ -281,11 +290,11 @@ void ContainerPage::showMainAction(const QList &actions)
void ContainerPage::showSigningButton()
{
if(cardInReader.isNull())
- showMainAction({ SignatureMobile });
+ showMainAction({ SignatureMobile, SignatureSmartID });
else if(seal)
- showMainAction({ SignatureToken, SignatureMobile });
+ showMainAction({ SignatureToken, SignatureMobile, SignatureSmartID });
else
- showMainAction({ SignatureAdd, SignatureMobile });
+ showMainAction({ SignatureAdd, SignatureMobile, SignatureSmartID });
}
void ContainerPage::transition(CryptoDoc* container, bool canDecrypt)
diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp
index c82727234..15249b2cc 100644
--- a/client/widgets/MainAction.cpp
+++ b/client/widgets/MainAction.cpp
@@ -90,6 +90,7 @@ QString MainAction::label(Actions action) const
switch(action)
{
case SignatureMobile: return tr("SignatureMobile");
+ case SignatureSmartID: return tr("SignatureSmartID");
case SignatureToken: return tr("SignatureToken");
case EncryptContainer: return tr("EncryptContainer");
case DecryptContainer: return tr("DecryptContainer");