diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 2d2235844d387d719c7ead432b0a41b6a1740b9c..e3325c05c2eb406b4f1de53b61d94d1afaa78cec 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -17,6 +17,7 @@
 
 #include <QApplication>
 #include <QImageReader>
+#include <QInputDialog>
 #include <QMessageBox>
 #include <QSettings>
 #include <QShortcut>
@@ -64,6 +65,8 @@ constexpr size_t MAX_ONETIME_KEYS         = 50;
 Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
 Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
 Q_DECLARE_METATYPE(mtx::presence::PresenceState)
+Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
+Q_DECLARE_METATYPE(SecretsToDecrypt)
 
 ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
   : QWidget(parent)
@@ -79,6 +82,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
         qRegisterMetaType<std::optional<RelatedInfo>>();
         qRegisterMetaType<mtx::presence::PresenceState>();
+        qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
+        qRegisterMetaType<SecretsToDecrypt>();
 
         topLayout_ = new QHBoxLayout(this);
         topLayout_->setSpacing(0);
@@ -136,6 +141,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         splitter->addWidget(content_);
         splitter->restoreSizes(parent->width());
 
+        connect(this,
+                &ChatPage::downloadedSecrets,
+                this,
+                &ChatPage::decryptDownloadedSecrets,
+                Qt::QueuedConnection);
+
         connect(this, &ChatPage::connectionLost, this, [this]() {
                 nhlog::net()->info("connectivity lost");
                 isConnected_ = false;
@@ -1208,3 +1219,45 @@ ChatPage::connectCallMessage()
                 view_manager_,
                 qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
 }
+
+void
+ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                                   const SecretsToDecrypt &secrets)
+{
+        QString text = QInputDialog::getText(
+          ChatPage::instance(),
+          QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
+          keyDesc.name.empty()
+            ? QCoreApplication::translate(
+                "CrossSigningSecrets",
+                "Enter your recovery key or passphrase to decrypt your secrets:")
+            : QCoreApplication::translate(
+                "CrossSigningSecrets",
+                "Enter your recovery key or passphrase called %1 to decrypt your secrets:")
+                .arg(QString::fromStdString(keyDesc.name)),
+          QLineEdit::Password);
+
+        if (text.isEmpty())
+                return;
+
+        auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc);
+
+        if (!decryptionKey)
+                decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
+
+        if (!decryptionKey) {
+                QMessageBox::information(
+                  ChatPage::instance(),
+                  QCoreApplication::translate("CrossSigningSecrets", "Decrytion failed"),
+                  QCoreApplication::translate("CrossSigningSecrets",
+                                              "Failed to decrypt secrets with the "
+                                              "provided recovery key or passphrase"));
+                return;
+        }
+
+        for (const auto &[secretName, encryptedSecret] : secrets) {
+                auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
+                if (!decrypted.empty())
+                        cache::storeSecret(secretName, decrypted);
+        }
+}
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 5b336cbb28fdfd9e9a08be9b11fc6c14ce31f5f5..45a4ff63105287133e76c2386c45fe445d1a8a64 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -27,6 +27,7 @@
 #include <mtx/events/encrypted.hpp>
 #include <mtx/events/member.hpp>
 #include <mtx/events/presence.hpp>
+#include <mtx/secret_storage.hpp>
 
 #include <QFrame>
 #include <QHBoxLayout>
@@ -72,6 +73,8 @@ namespace popups {
 class UserMentions;
 }
 
+using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
+
 class ChatPage : public QWidget
 {
         Q_OBJECT
@@ -117,6 +120,8 @@ public slots:
         void unbanUser(QString userid, QString reason);
 
         void receivedSessionKey(const std::string &room_id, const std::string &session_id);
+        void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                                      const SecretsToDecrypt &secrets);
 
 signals:
         void connectionLost();
@@ -185,6 +190,9 @@ signals:
         void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
         void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
 
+        void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                               const SecretsToDecrypt &secrets);
+
 private slots:
         void logout();
         void removeRoom(const QString &room_id);
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 82a61fba43abd3014e28f61801f1d550e7f54db3..0dbd51249ca6d2a2171217e231902c813dfd28dc 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -1243,8 +1243,73 @@ request_cross_signing_keys()
         request(mtx::secret_storage::secrets::cross_signing_user_signing);
         request(mtx::secret_storage::secrets::megolm_backup_v1);
 }
+
+namespace {
+void
+unlock_secrets(const std::string &key,
+               const std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData> &secrets)
+{
+        http::client()->secret_storage_key(
+          key,
+          [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                    mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::net()->error("Failed to download secret storage key");
+                          return;
+                  }
+
+                  emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets);
+          });
+}
+}
+
 void
 download_cross_signing_keys()
-{}
+{
+        using namespace mtx::secret_storage;
+        http::client()->secret_storage_secret(
+          secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) {
+                  std::optional<Secret> backup_key;
+                  if (!err)
+                          backup_key = secret;
+
+                  http::client()->secret_storage_secret(
+                    secrets::cross_signing_self_signing,
+                    [backup_key](Secret secret, mtx::http::RequestErr err) {
+                            std::optional<Secret> self_signing_key;
+                            if (!err)
+                                    self_signing_key = secret;
+
+                            http::client()->secret_storage_secret(
+                              secrets::cross_signing_user_signing,
+                              [backup_key, self_signing_key](Secret secret,
+                                                             mtx::http::RequestErr err) {
+                                      std::optional<Secret> user_signing_key;
+                                      if (!err)
+                                              user_signing_key = secret;
+
+                                      std::map<std::string,
+                                               std::map<std::string, AesHmacSha2EncryptedData>>
+                                        secrets;
+
+                                      if (backup_key && !backup_key->encrypted.empty())
+                                              secrets[backup_key->encrypted.begin()->first]
+                                                     [secrets::megolm_backup_v1] =
+                                                       backup_key->encrypted.begin()->second;
+                                      if (self_signing_key && !self_signing_key->encrypted.empty())
+                                              secrets[self_signing_key->encrypted.begin()->first]
+                                                     [secrets::cross_signing_self_signing] =
+                                                       self_signing_key->encrypted.begin()->second;
+                                      if (user_signing_key && !user_signing_key->encrypted.empty())
+                                              secrets[user_signing_key->encrypted.begin()->first]
+                                                     [secrets::cross_signing_user_signing] =
+                                                       user_signing_key->encrypted.begin()->second;
+
+                                      for (const auto &[key, secrets] : secrets)
+                                              unlock_secrets(key, secrets);
+                              });
+                    });
+          });
+}
 
 } // namespace olm
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index fe0145fe852f05b14e8844df99b8b16716d403bb..9fd76bdada303b6a33e29dc68d4f8141739a6979 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -1029,6 +1029,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
                 olm::request_cross_signing_keys();
         });
 
+        connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() {
+                olm::download_cross_signing_keys();
+        });
+
         connect(backBtn_, &QPushButton::clicked, this, [this]() {
                 settings_->save();
                 emit moveBack();