diff options
Diffstat (limited to 'src/auth_flow.cpp')
| -rw-r--r-- | src/auth_flow.cpp | 276 |
1 files changed, 194 insertions, 82 deletions
diff --git a/src/auth_flow.cpp b/src/auth_flow.cpp index 07544e8..8d164de 100644 --- a/src/auth_flow.cpp +++ b/src/auth_flow.cpp @@ -3,31 +3,55 @@ #include <QJsonObject> #include <QTableView> #include <QVBoxLayout> +#include <QMessageBox> +#include <QPushButton> +#include <QLabel> #include "auth_flow.h" -#include "keyring.h" #include "file_functions.h" +void AuthFlow::initialize(KeyringModel *model) +{ + m_model = model; + m_saved_auth.load(); +} + // This function is supposed to open the authentication dialog with various // fields like method selection and fields to enter password or select a key, -// but for now, it'll simply submit a public key auth request. Hostname -// parameter is unused. -void start_auth_flow(AOApplication *ao_app, QString username) +// but for now, it'll simply submit a public key auth request. +void AuthFlow::start(QString user, QString host) { - ao_app->ex_auth_username = username; - AuthRequest req; - req.username = username; - req.method = AuthMethod::certificate; - ao_app->send_ex_message(serializeAuthRequest(req)); + if (m_phase == AuthPhase::requested) + { + reset(); + } + + m_username = user; + m_hostname = host; + m_key_dlg = new KeySelectDialog(m_model, m_username, m_hostname); + m_key_dlg->setWindowModality(Qt::ApplicationModal); + connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected); + connect(m_key_dlg, &QDialog::rejected, this, &AuthFlow::reset); + + auto saved_key_id = m_saved_auth.lookup(host.toUtf8(), user.toUtf8()); + if (!saved_key_id.isEmpty()) + { + m_mode = FlowMode::Saved; + on_key_selected(acquire_keyring_key(saved_key_id)); + } + else + { + m_mode = FlowMode::Default; + m_key_dlg->show(); + } } -KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QWidget *parent) - : QDialog(parent) +KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QStringView hostname, QWidget *parent) + : QDialog(parent) , m_model(model) { - this->setAttribute(Qt::WA_DeleteOnClose); - this->setWindowTitle(QString("Select key for %1").arg(username)); + this->setWindowTitle(QString("Select key for %1@%2").arg(username, hostname)); auto view = new QTableView(this); view->setModel(m_model); view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); @@ -46,10 +70,9 @@ KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QWid return; } - QByteArray key_id = m_model->data(rows.first(), KeyringModel::KeyIDRole).toByteArray(); - QString key_name = m_model->data(rows.first()).toString(); + QByteArray selected_key_id = m_model->data(rows.first(), KeyringModel::KeyIDRole).toByteArray(); - emit key_selected(key_id, key_name); + emit key_selected(acquire_keyring_key(selected_key_id)); }); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [view, buttons](const QItemSelection &, const QItemSelection &) { bool selected = !view->selectionModel()->selectedRows().isEmpty(); @@ -57,15 +80,28 @@ KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QWid }); } -KeyPassphraseDialog::KeyPassphraseDialog(QStringView key_name, QWidget *parent) - : QDialog(parent) +KeyActivationDialog::KeyActivationDialog(Mode mode, QStringView key_name, QWidget *parent) + : QDialog(parent) { - this->setWindowTitle(QString("Enter passphrase for key %1").arg(key_name)); + this->setWindowTitle(QString("Unlock key %1").arg(key_name)); QVBoxLayout *pw_layout = new QVBoxLayout(this); - pw_layout->addWidget(new QLabel(QStringLiteral("Passphrase:"), this)); - m_pw_line = new QLineEdit(this); - m_pw_line->setEchoMode(QLineEdit::Password); - pw_layout->addWidget(m_pw_line); + + QString mode_prompt; + switch (mode) { + case Mode::passphrase: + mode_prompt = QStringLiteral("Passphrase:"); + break; + case Mode::pin: + mode_prompt = QStringLiteral("PIN:"); + break; + default: + break; + } + + pw_layout->addWidget(new QLabel(mode_prompt, this)); + m_line = new QLineEdit(this); + m_line->setEchoMode(QLineEdit::Password); + pw_layout->addWidget(m_line); QDialogButtonBox *pw_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); pw_layout->addWidget(pw_buttons); m_err_lbl = new QLabel(this); @@ -73,87 +109,163 @@ KeyPassphraseDialog::KeyPassphraseDialog(QStringView key_name, QWidget *parent) pw_layout->addWidget(m_err_lbl); connect(pw_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(pw_buttons, &QDialogButtonBox::accepted, this, [this] { - emit passphrase_submitted(m_pw_line->text().toUtf8()); - m_pw_line->clear(); - // zero the buffer + emit input_submitted(m_line->text().toUtf8()); + m_line->clear(); }); } -void KeyPassphraseDialog::display_error(ResponseResult error) const +QString auth_error_to_string(ResponseResult error) { - QString error_text; switch (error) { case ResponseResult::unsupported_version: - error_text = QStringLiteral("Unsupported key version."); - break; + return QStringLiteral("Unsupported key version."); case ResponseResult::decryption_failed: - error_text = QStringLiteral("Wrong passphrase (or corrupted key)."); - break; + return QStringLiteral("Wrong passphrase (or corrupted key)."); + case ResponseResult::hardware_fault: + return QStringLiteral("Hardware rejected this operation. It might be due to corrupted key, invalid server challenge, or a TPM fault."); + case ResponseResult::invalid_pin: + return QStringLiteral("Invalid PIN."); + case ResponseResult::hardware_lockout: + return QStringLiteral("Your hardware is locked out after too many failed PIN attempts. Try again later."); + case ResponseResult::incompatible_arguments: + return QStringLiteral("Server authentication incompatible with your key."); default: - error_text = QString("Error unlocking key (code %1). Catastrophic failure.").arg((int)error); - break; + return QString("Error unlocking key (code %1). Catastrophic failure.").arg((int)error); } - m_err_lbl->setText(error_text); +} + +void KeyActivationDialog::display_error(ResponseResult error) const +{ + m_err_lbl->setText(auth_error_to_string(error)); m_err_lbl->setVisible(true); } -AuthFlow::AuthFlow(AOApplication *ao_app, const AuthChallenge &challenge, QWidget *parent) - : QObject(parent) - , m_ao_app(ao_app) - , m_challenge(challenge) +void AuthFlow::on_key_selected(KeyringKey key) { - auto saved_key = ao_app->saved_auth.lookup(ao_app->m_serverdata.m_server_hostname.toUtf8(), ao_app->ex_auth_username.toUtf8()); - if (!saved_key.isEmpty()) + m_key = key; + // Choosing the key for the first request. + if (m_phase == AuthPhase::none) { - m_mode = FlowMode::Saved; - m_key_dlg = nullptr; - on_key_selected(saved_key, QString("%1@%2 (saved)").arg(ao_app->ex_auth_username, ao_app->m_serverdata.m_server_hostname)); + AuthRequest req = { + .username = m_username, + .method = AuthMethod::certificate, + .variant = m_key.type == KeyringStorageType::hardware ? CertVariant::hardware : CertVariant::software, + }; + + m_phase = AuthPhase::requested; + + emit request_ready(req); } - else + // Already authenticating; choosing a different key for the same request. + // This can be erroneous if a server challenge is issued for a different type + // of key (software X25519 vs hardware P-256), creating a mismatch. + else if (m_phase == AuthPhase::requested) { - m_mode = FlowMode::Default; - m_key_dlg = new KeySelectDialog(&ao_app->keyring_model, ao_app->ex_auth_username, parent); - connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected); - m_key_dlg->open(); + respond(m_challenge); } } -void AuthFlow::on_key_selected(QByteArrayView key_id, QStringView key_name) +void AuthFlow::authenticate(QByteArrayView input) { - m_pwd_dlg = new KeyPassphraseDialog(key_name, m_key_dlg); - connect(m_pwd_dlg, &KeyPassphraseDialog::passphrase_submitted, this, [this, key_id](QByteArrayView passphrase) { - AuthResponse response; - ResponseResult result = unlock_and_auth(key_id, passphrase, m_challenge.challenge, m_ao_app->ex_auth_username.toUtf8(), response); - if (result == ResponseResult::success) - { - m_ao_app->send_ex_message(serializeAuthResponse(response)); - if (m_mode == FlowMode::Default) - { - m_key_dlg->accept(); - m_ao_app->saved_auth.insert(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8(), key_id); - } - else - { - m_pwd_dlg->accept(); - } - deleteLater(); - } - else + AuthResponse response; + ResponseResult result = unlock_and_auth(m_key, input, m_challenge.challenge, m_username.toUtf8(), response); + if (result == ResponseResult::success) + { + emit response_ready(response); + if (m_mode == FlowMode::Default) { - m_pwd_dlg->display_error(result); + m_saved_auth.insert(m_hostname.toUtf8(), m_username.toUtf8(), m_key.id); } - }); - connect(m_pwd_dlg, &QDialog::rejected, this, [this] { - if (m_mode == FlowMode::Saved) - { - m_ao_app->saved_auth.remove(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8()); - m_mode = FlowMode::Default; + reset(); + } + // The error behavior depends on whether we need the application-level key + // unlock dialog (for TSS2) or not (for Windows CNG, which uses its own). + else if (m_input_dlg) + { + m_input_dlg->display_error(result); + } + else + { + QMessageBox::warning(nullptr, "Error", auth_error_to_string(result)); + reject_and_select_new_key(); + } +} - m_key_dlg = new KeySelectDialog(&m_ao_app->keyring_model, m_ao_app->ex_auth_username); - connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected); - m_key_dlg->open(); - } - }); - m_pwd_dlg->open(); +void AuthFlow::reject_and_select_new_key() +{ + if (m_mode == FlowMode::Saved) + { + m_saved_auth.remove(m_hostname.toUtf8(), m_username.toUtf8()); + m_mode = FlowMode::Default; + } + + if (m_input_dlg) + { + m_input_dlg->deleteLater(); + } + + // The key selection dialog remains in the memory while the authentication + // attempt is performed. It's only cleared on finalization of the entire flow. + m_key_dlg->show(); +} + +void AuthFlow::call_input_dialog(KeyActivationDialog::Mode mode) +{ + m_input_dlg = new KeyActivationDialog(mode, m_key.name); + m_input_dlg->setWindowModality(Qt::ApplicationModal); + + connect(m_input_dlg, &KeyActivationDialog::input_submitted, this, &AuthFlow::authenticate); + connect(m_input_dlg, &QDialog::rejected, this, &AuthFlow::reject_and_select_new_key); + m_key_dlg->hide(); + m_input_dlg->show(); +} + +bool AuthFlow::awaiting_challenge() const +{ + return m_phase == AuthPhase::requested; +} + +void AuthFlow::respond(const AuthChallenge &challenge) +{ + if (m_phase != AuthPhase::requested) + { + return; + } + + m_challenge = challenge; + + switch (m_key.type) { + case KeyringStorageType::software: + call_input_dialog(KeyActivationDialog::Mode::passphrase); + break; + case KeyringStorageType::hardware: +#ifdef SOF_AO_HARDWARE_KEY_REQUIRES_PIN_UI + call_input_dialog(KeyActivationDialog::Mode::pin); +#else + m_key_dlg->hide(); + authenticate(QByteArray()); +#endif + break; + default: + QMessageBox::warning(nullptr, "Error", QStringLiteral("Unable to use this key.")); + break; + } +} + +void AuthFlow::reset() +{ + if (m_key_dlg) + { + m_key_dlg->deleteLater(); + m_key_dlg = nullptr; + } + + if (m_input_dlg) + { + m_input_dlg->deleteLater(); + m_input_dlg = nullptr; + } + + m_phase = AuthPhase::none; } |
