diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 6675c6b9b..cae8ece83 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -431,20 +431,22 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy if ((d->version == 2) && Settings::CDOC2_USE_KEYSERVER) { keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; } - // Encrypt for address list - for (const auto &key : d->keys) { - if (!key.rcpt_cert.isNull()) { - QByteArray ba = key.rcpt_cert.toDer(); - enc_keys.push_back(libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()}, keyserver_id)); - } else { - switch (key.lock.type) { - case libcdoc::Lock::CDOC1: - case libcdoc::Lock::PUBLIC_KEY: - case libcdoc::Lock::SERVER: - enc_keys.push_back(libcdoc::Recipient::makePublicKey(key.lock, keyserver_id)); - break; - default: - break; + // Encrypt for address list, password encryption excludes recipients + if (secret.isEmpty()) { + for (const auto &key : d->keys) { + if (!key.rcpt_cert.isNull()) { + QByteArray ba = key.rcpt_cert.toDer(); + enc_keys.push_back(libcdoc::Recipient::makeCertificate({}, {ba.cbegin(), ba.cend()}, keyserver_id)); + } else { + switch (key.lock.type) { + case libcdoc::Lock::CDOC1: + case libcdoc::Lock::PUBLIC_KEY: + case libcdoc::Lock::SERVER: + enc_keys.push_back(libcdoc::Recipient::makePublicKey(key.lock, keyserver_id)); + break; + default: + break; + } } } } diff --git a/client/common_enums.h b/client/common_enums.h index ae8d733ab..183c1f227 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -43,7 +43,6 @@ enum Actions : unsigned char { ContainerEncrypt, EncryptContainer, - EncryptLT, EncryptContainerSuccess, DecryptContainer, DecryptContainerSuccess, diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp index 818e7c641..09257c6e4 100644 --- a/client/dialogs/PasswordDialog.cpp +++ b/client/dialogs/PasswordDialog.cpp @@ -36,8 +36,9 @@ PasswordDialog::PasswordDialog(Mode mode, QWidget *parent) ui->password2Line->setHidden(mode == Mode::DECRYPT); ui->password2Error->hide(); if(mode == DECRYPT) { - ui->title->setText(tr("Decrypt with password")); - ui->passwordLabel->setText(tr("Enter password to decrypt the document")); + ui->title->setText(tr("Decrypt")); + ui->labelLabel->setText(tr("Envelope name")); + ui->passwordLabel->setText(tr("Envelope password")); ui->ok->setText(tr("Decrypt")); ui->passwordLine->setFocus(); } diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui index d43d7f401..7597ed751 100644 --- a/client/dialogs/PasswordDialog.ui +++ b/client/dialogs/PasswordDialog.ui @@ -37,6 +37,12 @@ font-size: 16px; QLineEdit[label="error"] { border-color: #BE7884; } +QLineEdit[readOnly="true"] { +border: none; +padding: 0px; +background-color: transparent; +font-weight: 700; +} QLineEdit::disabled { color: #607496; background-color: #F3F5F7; @@ -114,30 +120,6 @@ margin-left: 6px; - - - - - - Key label (recipient name or id) - - - labelLine - - - - - - - - 400 - 0 - - - - - - @@ -174,6 +156,36 @@ margin-left: 6px; + + + + 6 + + + + + Create a name for the envelope + + + labelLine + + + + + + + + 400 + 0 + + + + E.g. Contracts + + + + + @@ -182,7 +194,7 @@ margin-left: 6px; - Enter a password to encrypt the document + Create a password for the envelope Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter diff --git a/client/images/icon_check_white.svg b/client/images/icon_check_white.svg new file mode 100644 index 000000000..b94b204fa --- /dev/null +++ b/client/images/icon_check_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/images.qrc b/client/images/images.qrc index 667f1a4e9..2537f5f19 100644 --- a/client/images/images.qrc +++ b/client/images/images.qrc @@ -23,6 +23,7 @@ icon_checkbox_active.svg icon_checkbox_disabled.svg icon_checkbox_hover.svg + icon_check_white.svg icon_checkbox_check.svg icon_checkbox_check_active.svg icon_checkbox_check_disabled.svg diff --git a/client/translations/en.ts b/client/translations/en.ts index 496dee821..91cec8645 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -493,6 +493,14 @@ DigiDoc4 Client DigiDoc4 Client + + Encrypt for recipients + Encrypt for recipients + + + Encrypt with password + Encrypt with password + Decrypting Decrypting @@ -529,6 +537,10 @@ Change Change + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Cancel Cancel @@ -1316,12 +1328,6 @@ LDAP server is unavailable. Sign Sign - - Encrypt -long-term - Encrypt -long-term - MainWindow @@ -1714,16 +1720,24 @@ long-term Encrypt with password Encrypt with password - - Key label (recipient name or id) - Key label (recipient name or id) - Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. + + Create a name for the envelope + Create a name for the envelope + + + E.g. Contracts + E.g. Contracts + + + Create a password for the envelope + Create a password for the envelope + • Length: 20–64 characters • Contains at least one number (0–9) @@ -1747,16 +1761,16 @@ long-term Encrypt - Decrypt with password - Decrypt with password + Decrypt + Decrypt - Enter password to decrypt the document - Enter password to decrypt the document + Envelope name + Envelope name - Decrypt - Decrypt + Envelope password + Envelope password Password is empty @@ -1770,10 +1784,6 @@ long-term Passwords do not match Passwords do not match - - Enter a password to encrypt the document - Enter a password to encrypt the document - PinPopup diff --git a/client/translations/et.ts b/client/translations/et.ts index 704acf724..43fdce51c 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -493,6 +493,14 @@ DigiDoc4 Client DigiDoc4 klient + + Encrypt for recipients + Krüpteeri adressaadi alusel + + + Encrypt with password + Krüpteeri parooliga + Decrypting Dekrüpteerin @@ -529,6 +537,10 @@ Change Muuda + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + Parooliga krüpteerimine on mõeldud pikaajaliseks salvestamiseks. Parooli ei saa muuta ega taastada. + Cancel Katkesta @@ -1316,12 +1328,6 @@ LDAP serveriga ei saa ühendust. Sign Allkirjasta - - Encrypt -long-term - Krüpteeri -säilitamiseks - MainWindow @@ -1714,16 +1720,24 @@ säilitamiseks Encrypt with password Krüpteeri parooliga - - Key label (recipient name or id) - Võtme nimi (saaja nimi või kood) - Be sure to save the password in a secure place - without the password, you won’t be able to open the file again. Salvesta parool kindlasti turvalisse kohta - ilma paroolita ei saa faili enam avada. + + Create a name for the envelope + Loo ümbrikule nimi + + + E.g. Contracts + Nt. Lepingud + + + Create a password for the envelope + Loo ümbrikule parool + • Length: 20–64 characters • Contains at least one number (0–9) @@ -1747,16 +1761,16 @@ säilitamiseks Krüpteeri - Decrypt with password - Dekrüpteeri parooliga + Decrypt + Dekrüpteeri - Enter password to decrypt the document - Sisestage parool dokumendi dekrüpteerimiseks + Envelope name + Ümbriku nimi - Decrypt - Dekrüpteeri + Envelope password + Ümbriku parool Password is empty @@ -1768,11 +1782,7 @@ säilitamiseks Passwords do not match - Paroolid ei ühti - - - Enter a password to encrypt the document - Loo ümbrikule parool + Paroolid ei kattu diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index c2067be59..9cf7447c7 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,19 @@ ContainerPage::ContainerPage(QWidget *parent) connect(ui->leftPane, &FileList::addFiles, this, &ContainerPage::addFiles); connect(ui->leftPane, &ItemList::addItem, this, [this](int code) { emit action(code); }); connect(ui->rightPane, &ItemList::addItem, this, [this](int code) { emit action(code); }); + ui->encryptMethodArea->hide(); + ui->encryptMethod->setExpanding(true); + ui->encryptMethod->setDrawBase(false); + ui->encryptMethod->setIconSize({16, 16}); + ui->encryptMethod->addTab({}); + ui->encryptMethod->addTab({}); + connect(ui->encryptMethod, &QTabBar::currentChanged, this, [this](int index) { + for(int i = 0; i < ui->encryptMethod->count(); ++i) + ui->encryptMethod->setTabIcon(i, i == index ? + QIcon(QStringLiteral(":/images/icon_check_white.svg")) : QIcon()); + ui->rightPaneStack->setCurrentIndex(index); + }); + ui->encryptMethod->setTabIcon(0, QIcon(QStringLiteral(":/images/icon_check_white.svg"))); connect(ui->email, &QAbstractButton::clicked, this, [this] { if(!QFileInfo::exists(fileName)) return; @@ -160,12 +174,12 @@ void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, con } template -void ContainerPage::deleteConfirm(C *c, int index) +bool ContainerPage::deleteConfirm(C *c, int index) { if(c->documentModel()->rowCount() > 1) { ui->leftPane->removeItem(index); - return; + return true; } auto *dlg = WarningDialog::create(this) ->withTitle(tr("You are about to delete the last file in the container")) @@ -174,33 +188,34 @@ void ContainerPage::deleteConfirm(C *c, int index) ->resetCancelStyle(false) ->addButton(WarningDialog::Remove, QMessageBox::Ok, true); if (dlg->exec() != QMessageBox::Ok) - return; + return false; window()->setWindowFilePath({}); window()->setWindowTitle(QCoreApplication::translate("MainWindow", "DigiDoc4 Client")); if(QFile::exists(c->fileName())) QFile::remove(c->fileName()); emit action(ContainerClose); + return false; } -void ContainerPage::encrypt(CryptoDoc *container, bool longTerm) +void ContainerPage::encrypt(CryptoDoc *container) { - if(!longTerm) { - WaitDialogHolder waitDialog(this, tr("Encrypting")); - if(!container->encrypt(container->fileName(), {}, {})) + QString label; + QByteArray secret; + QSslCertificate cert; + if(isPasswordEncryption()) { + PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); + if(!p.exec()) return; - transition(container, qApp->cryptoManager()->tokenauth().cert()); - emit action(EncryptContainerSuccess); - return; + label = p.label(); + secret = p.secret(); + } else { + cert = qApp->cryptoManager()->tokenauth().cert(); } - PasswordDialog p(PasswordDialog::Mode::ENCRYPT, this); - if(!p.exec()) - return; - WaitDialogHolder waitDialog(this, tr("Encrypting")); - if(!container->encrypt(container->fileName(), p.label(), p.secret())) + if(!container->encrypt(container->fileName(), label, secret)) return; - transition(container, QSslCertificate{}); + transition(container, cert); emit action(EncryptContainerSuccess); } @@ -212,36 +227,9 @@ void ContainerPage::setHeader(const QString &file) elideFileName(); } -void ContainerPage::showEncryptAction(CryptoDoc *container) -{ - if (!container->keys().empty()) { - mainAction->showAction(EncryptContainer); - mainAction->setEnabled(true); - } else if (container->supportsSymmetricKeys()) { - mainAction->showAction(EncryptLT); - mainAction->setEnabled(true); - } else { - mainAction->showAction(EncryptContainer); - mainAction->setEnabled(false); - } - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); -} - -void ContainerPage::showSigningButton() +bool ContainerPage::isPasswordEncryption() const { - if (!isSupported) - { - mainAction->hide(); - ui->mainActionSpacer->changeSize(1, 20, QSizePolicy::Fixed); - } - else - { - mainAction->showAction(SignatureAdd); - mainAction->setEnabled(isSupported); - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - } - ui->navigationArea->layout()->invalidate(); + return !ui->encryptMethodArea->isHidden() && ui->encryptMethod->currentIndex() == 1; } void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert) @@ -262,19 +250,19 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert container->addEncryptionKey(key); ui->rightPane->addWidget(new AddressItem(key, AddressItem::Icon, ui->rightPane)); } - showEncryptAction(container); + mainAction->setEnabled(isEncryptEnabled(container)); }); disconnect(ui->rightPane, &ItemList::removed, container, nullptr); connect(ui->rightPane, &ItemList::removed, container, [this, container](int index) { container->removeKey(index); ui->rightPane->removeItem(index); - showEncryptAction(container); + mainAction->setEnabled(isEncryptEnabled(container)); }); disconnect(this, &ContainerPage::certChanged, container, nullptr); connect(this, &ContainerPage::certChanged, container, [this, container](const SslCertificate &cert) { isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); if(container->state() & EncryptedContainer) - updateDecryptionButton(); + mainAction->setEnabled(isSupported && !isBlocked); }); disconnect(ui->changeLocation, &QPushButton::clicked, container, nullptr); connect(ui->changeLocation, &QPushButton::clicked, container, [container, this] { @@ -291,15 +279,16 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert connect(container, &CryptoDoc::destroyed, this, [this] { clear(ContainerClearWarning); }); + disconnect(ui->encryptMethod, &QTabBar::currentChanged, container, nullptr); + connect(ui->encryptMethod, &QTabBar::currentChanged, container, [this, container] { + mainAction->setEnabled(isEncryptEnabled(container)); + }); disconnect(mainAction, &MainAction::action, container, nullptr); connect(mainAction, &MainAction::action, container, [container, this](int action) { switch (action) { case EncryptContainer: - encrypt(container, false); - break; - case EncryptLT: - encrypt(container, true); + encrypt(container); break; case DecryptContainer: decrypt(container, nullptr, {}); @@ -310,6 +299,7 @@ void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert }); clear(ContainerClearWarning); + ui->encryptMethod->setCurrentIndex(0); isSupported = container->state() & UnencryptedContainer || container->canDecrypt(cert); setHeader(container->fileName()); ui->leftPane->init(fileName, QT_TRANSLATE_NOOP("ItemList", "Encrypted files")); @@ -341,7 +331,8 @@ void ContainerPage::transition(DigiDoc* container) using enum WarningText::WarningType; disconnect(ui->leftPane, &ItemList::removed, container, nullptr); connect(ui->leftPane, &ItemList::removed, container, [this, container](int index) { - deleteConfirm(container, index); + if(deleteConfirm(container, index)) + transition(container); }); disconnect(ui->summary, &QAbstractButton::clicked, container, nullptr); connect(ui->summary, &QAbstractButton::clicked, container, [this,container] { @@ -458,21 +449,21 @@ void ContainerPage::transition(DigiDoc* container) updatePanes(container->state(), nullptr); } -void ContainerPage::updateDecryptionButton() +bool ContainerPage::isEncryptEnabled(CryptoDoc *container) const { - mainAction->showAction(DecryptContainer); - mainAction->setEnabled(isSupported && !isBlocked); - ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); - ui->navigationArea->layout()->invalidate(); + return isPasswordEncryption() || (container && !container->keys().empty()); } void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container) { ui->leftPane->stateChange(state); ui->rightPane->stateChange(state); + ui->encryptMethodArea->setVisible(state == UnencryptedContainer && crypto_container && crypto_container->supportsSymmetricKeys()); + if(ui->encryptMethodArea->isHidden()) + ui->encryptMethod->setCurrentIndex(0); ui->save->setVisible(state == UnsignedContainer); - ui->rightPane->setHidden(state == UnsignedContainer); - auto setButtonsVisible = [](const QVector &buttons, bool visible) { + ui->rightPaneArea->setHidden(state == UnsignedContainer); + auto setButtonsVisible = [](std::initializer_list buttons, bool visible) { for(QWidget *button: buttons) button->setVisible(visible); }; @@ -483,7 +474,6 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->show(); ui->rightPane->clear(); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email, ui->summary, ui->extend }, false); break; case UnsignedSavedContainer: @@ -492,7 +482,6 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->show(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container is not signed")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email }, true); ui->extend->hide(); break; @@ -502,29 +491,53 @@ void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc ui->changeLocation->hide(); ui->rightPane->init(ItemList::ItemSignature, QT_TRANSLATE_NOOP("ItemList", "Container signatures")); ui->summary->setVisible(Settings::SHOW_PRINT_SUMMARY); - showSigningButton(); setButtonsVisible({ ui->saveAs, ui->email, ui->extend }, true); break; case UnencryptedContainer: cancelText = QT_TR_NOOP("Start"); convertText = QT_TR_NOOP("Sign"); - showEncryptAction(crypto_container); setButtonsVisible({ ui->changeLocation, ui->convert }, true); setButtonsVisible({ ui->saveAs, ui->email, ui->extend }, false); break; case EncryptedContainer: cancelText = QT_TR_NOOP("Start"); convertText = QT_TR_NOOP("Sign"); - updateDecryptionButton(); - setButtonsVisible({ ui->changeLocation, ui->convert }, false); + setButtonsVisible({ ui->changeLocation, ui->convert, ui->extend }, false); setButtonsVisible({ ui->saveAs, ui->email }, true); - ui->extend->hide(); break; default: // Uninitialized cannot be shown on container page break; } + switch(state) + { + case UnsignedContainer: + case UnsignedSavedContainer: + case SignedContainer: + if(isSupported) + { + mainAction->showAction(SignatureAdd); + mainAction->setEnabled(true); + } + else + mainAction->hide(); + break; + case UnencryptedContainer: + mainAction->showAction(EncryptContainer); + mainAction->setEnabled(isEncryptEnabled(crypto_container)); + break; + case EncryptedContainer: + mainAction->showAction(DecryptContainer); + mainAction->setEnabled(isSupported && !isBlocked); + break; + default: + mainAction->hide(); + break; + } + ui->mainActionSpacer->changeSize(mainAction->isHidden() ? 1 : 198, 20, QSizePolicy::Fixed); + ui->navigationArea->layout()->invalidate(); + translateLabels(); } @@ -537,4 +550,6 @@ void ContainerPage::translateLabels() { ui->cancel->setText(tr(cancelText)); ui->convert->setText(tr(convertText)); + ui->encryptMethod->setTabText(0, tr("Encrypt for recipients")); + ui->encryptMethod->setTabText(1, tr("Encrypt with password")); } diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index 8519c1dec..e9f051425 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -61,13 +61,12 @@ class ContainerPage final : public QWidget void clear(int code); void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret); template - void deleteConfirm(C *c, int index); + bool deleteConfirm(C *c, int index); void elideFileName(); - void encrypt(CryptoDoc *container, bool longTerm); + void encrypt(CryptoDoc *container); bool eventFilter(QObject *o, QEvent *e) final; - void showEncryptAction(CryptoDoc *container); - void showSigningButton(); - void updateDecryptionButton(); + bool isPasswordEncryption() const; + bool isEncryptEnabled(CryptoDoc *container) const; void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); void translateLabels(); diff --git a/client/widgets/ContainerPage.ui b/client/widgets/ContainerPage.ui index 4314ac3eb..ac172db3a 100644 --- a/client/widgets/ContainerPage.ui +++ b/client/widgets/ContainerPage.ui @@ -11,9 +11,38 @@ - #leftPane, #rightPane { + #leftPane, #rightPaneArea { background-color: #ffffff; } +QTabBar::tab { +padding: 11px 19px; +color: #003168; +font-weight: 700; +background-color: #FFFFFF; +border: 1px solid #003168; +border-width: 1px 0px 1px 1px; +} +QTabBar::tab:first { +border-top-left-radius: 4px; +border-bottom-left-radius: 4px; +} +QTabBar::tab:last { +border-top-right-radius: 4px; +border-bottom-right-radius: 4px; +border-right-width: 1px; +} +QTabBar::tab:selected { +background-color: #003168; +color: #FFFFFF; +} +QTabBar::tab:hover:!selected { +background-color: #EDF1F6; +} +#passwordMethodInfo { +color: #07142A; +font-family: Roboto, Helvetica; +font-size: 14px; +} #leftPane { border: solid #F3F5F7; border-width: 0px 1px 0px 0px; @@ -149,7 +178,7 @@ background-color: #BFD3E8; - + 0 @@ -157,7 +186,132 @@ background-color: #BFD3E8; - + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 32 + + + 32 + + + 32 + + + 0 + + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 32 + + + 24 + + + 32 + + + 0 + + + + + Password encryption is meant for long-term storage. The password cannot be changed or recovered. + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + @@ -331,6 +485,11 @@ background-color: #E1C1C6; + + QTabBar + QWidget +
QTabBar
+
ItemList QScrollArea diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index 708685207..19c6cbc1e 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -77,7 +77,6 @@ void MainAction::update() switch(_action) { case EncryptContainer: return setText(tr("Encrypt")); - case EncryptLT: return setText(tr("Encrypt\nlong-term")); case DecryptContainer: return setText(tr("Decrypt")); default: return setText(tr("Sign")); }