diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 5aea818aa..0f6b35d4e 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -246,6 +246,14 @@ DDConfiguration::shareServersKnown(const std::string &shareUrls) !shareServer(QLatin1String("CDOC2-SHARES-RP"), QLatin1String("RP"), shareUrls).empty(); } +std::string +DDConfiguration::defaultShareServer() +{ + QString id = Settings::CDOC2_DEFAULT_KEYSERVER; + QJsonObject data = Application::confValue(QLatin1String("CDOC2-CONF")).toObject().value(id).toObject(); + return data.value(QLatin1String("SHARES")).toArray().size() >= 2 ? id.toStdString() : std::string(); +} + std::string DDConfiguration::getValue(std::string_view domain, std::string_view param) const { diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 80daf7c90..d5004d15b 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -53,6 +53,9 @@ struct DDConfiguration : public libcdoc::Configuration { // Whether the authentication and relying party servers for a lock's share // servers (Lock::SHARE_URLS) are configured, i.e. Mobile-ID / Smart-ID can open it static bool shareServersKnown(const std::string &shareUrls); + // The default key server's CDOC2-CONF entry, when it lists the two key share + // servers libcdoc needs to encrypt for Mobile-ID / Smart-ID; empty otherwise + static std::string defaultShareServer(); // Mobile-ID / Smart-ID decryption: Configuration::SHARE_SIGNER_MID or // SHARE_SIGNER_SID, the Mobile-ID phone number, and the lock's share servers // (Lock::SHARE_URLS) that select the CDOC2-CONF entry diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a5ddaf99c..bb67c7fb6 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -56,6 +56,15 @@ CKey::CKey(QSslCertificate _rcpt_cert) : lock(libcdoc::Lock::PUBLIC_KEY), rcpt_c lock.setBytes(libcdoc::Lock::CERT, std::vector(der.cbegin(), der.cend())); } +CKey +CKey::share(const QString &personalCode) +{ + libcdoc::Lock lock(libcdoc::Lock::SHARE_SERVER); + // The form CDoc2 stores it in, so a new recipient reads the same as an encrypted one + lock.setString(libcdoc::Lock::RECIPIENT_ID, "etsi/PNOEE-" + personalCode.toStdString()); + return CKey(lock); +} + bool CKey::canDecrypt(const QSslCertificate &cert) const { @@ -94,6 +103,8 @@ CKey::isSupported() const bool CKey::operator==(const CKey& rhs) const { + if (lock.type == libcdoc::Lock::SHARE_SERVER && rhs.lock.type == libcdoc::Lock::SHARE_SERVER) + return lock.getString(libcdoc::Lock::RECIPIENT_ID) == rhs.lock.getString(libcdoc::Lock::RECIPIENT_ID); if (!lock.isPKI() || !rhs.lock.isPKI()) return false; return lock.getBytes(libcdoc::Lock::RCPT_KEY) == rhs.lock.getBytes(libcdoc::Lock::RCPT_KEY); } @@ -265,6 +276,12 @@ CryptoDoc::supportsSymmetricKeys() const return !d->reader && (d->version == 2); } +bool +CryptoDoc::supportsKeyShares() const +{ + return supportsSymmetricKeys() && !DDConfiguration::defaultShareServer().empty(); +} + bool CryptoDoc::addEncryptionKey(const CKey& key) { if(d->isEncryptedWarning(tr("Failed to add key"))) return false; @@ -489,6 +506,16 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy ->open(); return false; } + const std::string share_server = DDConfiguration::defaultShareServer(); + if(std::ranges::any_of(d->keys, [](const CKey &key) { return key.lock.type == libcdoc::Lock::SHARE_SERVER; }) && + (d->version != 2 || share_server.empty())) + { + WarningDialog::create() + ->withTitle(tr("Failed to encrypt document")) + ->withText(tr("Key share servers for Mobile-ID and Smart-ID are not configured")) + ->open(); + return false; + } QString writer_last_error; auto keepAlive = FileDialog::keepAccessAlive(d->fileName); libcdoc::result_t result = waitFor([&] -> libcdoc::result_t { @@ -514,6 +541,11 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy case libcdoc::Lock::SERVER: enc_keys.push_back(libcdoc::Recipient::makePublicKey(key.lock, keyserver_id)); break; + case libcdoc::Lock::SHARE_SERVER: + // "etsi/PNOEE-..." -> "PNOEE-...", libcdoc adds the prefix back + enc_keys.push_back(libcdoc::Recipient::makeShare({}, share_server, + key.lock.getString(libcdoc::Lock::RECIPIENT_ID).substr(5))); + break; default: break; } diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 20c24f71e..1b52db620 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -44,6 +44,8 @@ struct CKey { public: CKey(const libcdoc::Lock& _lock) : lock(_lock) {} CKey(QSslCertificate rcpt_cert); + // Mobile-ID / Smart-ID recipient, encrypted for through key share servers + static CKey share(const QString &personalCode); libcdoc::Lock lock; QSslCertificate rcpt_cert; @@ -63,6 +65,7 @@ class CryptoDoc final: public QObject ~CryptoDoc() final; bool supportsSymmetricKeys() const; + bool supportsKeyShares() const; bool addEncryptionKey(const CKey& key); void clear(const QString &file = {}, int version = -1); bool decrypt(const libcdoc::Lock &lock, const QByteArray& secret, std::unique_ptr &&backend); diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index a541befdf..0cbc8d592 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -51,9 +51,10 @@ struct AddRecipients::Private: public Ui::AddRecipients QPushButton *btnFind; }; -AddRecipients::AddRecipients(ItemList* itemList, QWidget *parent) +AddRecipients::AddRecipients(ItemList* itemList, bool keyShares, QWidget *parent) : QDialog(parent) , ui(new Private) + , keyShares(keyShares) { #ifndef Q_OS_WIN if(const auto list = Application::confValue(QLatin1String("LDAP-CERTS")).toArray(); @@ -235,7 +236,11 @@ void AddRecipients::addRecipientFromHistory() void AddRecipients::addRecipient(const QSslCertificate& cert, bool select) { - CKey key(cert); + addRecipient(CKey(cert), select); +} + +void AddRecipients::addRecipient(const CKey &key, bool select) +{ AddressItem *leftItem = itemListValue(ui->leftPane, key); if(!leftItem) { @@ -257,7 +262,8 @@ void AddRecipients::addRecipientToRightPane(Item *item, bool update) const CKey& key = address->getKey(); if (rightList.contains(key)) return; - if(update) + // A Mobile-ID / Smart-ID recipient has no certificate to check + if(update && !key.rcpt_cert.isNull()) { if(auto expiryDate = key.rcpt_cert.expiryDate(); expiryDate <= QDateTime::currentDateTime()) { @@ -367,6 +373,9 @@ void AddRecipients::search(const QString &term, bool select, const QString &type ldap_corp->search(QStringLiteral("(serialNumber=%1)" ).arg(cleanTerm), userData); else if(IKValidator::isValid(cleanTerm)) { + // Mobile-ID / Smart-ID certificates are not in LDAP; any person can be offered it + if(keyShares) + addRecipient(CKey::share(cleanTerm), false); userData[QStringLiteral("personSearch")] = true; for(auto *ldap: ldap_person) { ldap->search(QStringLiteral("(serialNumber=PNOEE-%1)").arg(cleanTerm), userData); diff --git a/client/dialogs/AddRecipients.h b/client/dialogs/AddRecipients.h index b94382494..6bd487390 100644 --- a/client/dialogs/AddRecipients.h +++ b/client/dialogs/AddRecipients.h @@ -39,7 +39,8 @@ class AddRecipients final : public QDialog Q_OBJECT public: - explicit AddRecipients(ItemList* itemList, QWidget *parent = nullptr); + // keyShares offers Mobile-ID / Smart-ID for a searched personal code + explicit AddRecipients(ItemList* itemList, bool keyShares, QWidget *parent = nullptr); ~AddRecipients() final; QList keys() const; @@ -50,6 +51,7 @@ class AddRecipients final : public QDialog void addRecipientFromFile(); void addRecipientFromHistory(); void addRecipient(const QSslCertificate& cert, bool select = true); + void addRecipient(const CKey &key, bool select); void addRecipientToRightPane(Item *item, bool update = true); static AddressItem* itemListValue(ItemList *list, const CKey &key); @@ -63,6 +65,7 @@ class AddRecipients final : public QDialog QList ldap_person; LdapSearch *ldap_corp; int multiSearch = 0; + const bool keyShares; QTemporaryFile ldapCACerts; HistoryList historyCertData; diff --git a/client/translations/en.ts b/client/translations/en.ts index b30caa76c..710bee4dd 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -643,6 +643,10 @@ Key transfer server is not configured Key transfer server is not configured + + Key share servers for Mobile-ID and Smart-ID are not configured + Key share servers for Mobile-ID and Smart-ID are not configured + Failed to remove key Failed to remove key diff --git a/client/translations/et.ts b/client/translations/et.ts index 64aef3669..b2fe91af2 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -643,6 +643,10 @@ Key transfer server is not configured Võtmeedastusserver ei ole seadistatud + + Key share servers for Mobile-ID and Smart-ID are not configured + Mobiil-ID ja Smart-ID võtmeosade servereid ei ole seadistatud + Failed to remove key Võtme eemaldamine ebaõnnestus diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index 9c0901df7..d43d0b007 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -225,7 +225,7 @@ void ContainerPage::transition(CryptoDoc *container) }); disconnect(ui->rightPane, &ItemList::add, container, nullptr); connect(ui->rightPane, &ItemList::add, container, [this, container] { - AddRecipients dlg(ui->rightPane, this); + AddRecipients dlg(ui->rightPane, container->supportsKeyShares(), this); if(!dlg.exec() || !dlg.isUpdated()) return; container->clearKeys();