Files
nixos-workstations/workstation-setup/src/ProvisioningBackend.cpp
T

747 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ProvisioningBackend.h"
#include "Config.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QSaveFile>
#include <QTimer>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
namespace {
constexpr int kHelperTimeoutMs = 120000;
bool containsLineBreak(const QString &value)
{
return value.contains(QLatin1Char('\n')) || value.contains(QLatin1Char('\r'));
}
QString effectiveHomeDirectory()
{
if (passwd *entry = getpwuid(geteuid())) {
const QString home = QString::fromLocal8Bit(entry->pw_dir);
if (!home.isEmpty() && home != QStringLiteral("/")) {
return home;
}
}
return QDir::homePath();
}
}
ProvisioningBackend::ProvisioningBackend(QString targetUser, QObject *parent)
: QObject(parent)
, m_currentUser(effectiveUserName())
, m_targetUser(std::move(targetUser))
{
refreshState();
appendLog(QStringLiteral("START currentUser=%1 targetUser=%2 authorized=%3 passwordDone=%4 pinDone=%5 fidoDone=%6 yubiKeyChoice=%7 yubiKeyRequested=%8")
.arg(m_currentUser,
m_targetUser,
authorizedUser() ? QStringLiteral("yes") : QStringLiteral("no"),
m_passwordDone ? QStringLiteral("yes") : QStringLiteral("no"),
m_pinDone ? QStringLiteral("yes") : QStringLiteral("no"),
m_fidoDone ? QStringLiteral("yes") : QStringLiteral("no"),
yubiKeyChoiceMade() ? QStringLiteral("yes") : QStringLiteral("no"),
yubiKeyRequested() ? QStringLiteral("yes") : QStringLiteral("no")));
}
ProvisioningBackend::~ProvisioningBackend()
{
secureClear(m_pendingPayload);
if (m_process && m_process->state() != QProcess::NotRunning) {
m_process->kill();
m_process->waitForFinished(1000);
}
}
QString ProvisioningBackend::effectiveUserName()
{
if (passwd *entry = getpwuid(geteuid())) {
return QString::fromLocal8Bit(entry->pw_name);
}
return {};
}
bool ProvisioningBackend::authorizedUser() const
{
if (m_targetUser.isEmpty()) {
return false;
}
const QByteArray name = m_targetUser.toLocal8Bit();
if (passwd *entry = getpwnam(name.constData())) {
return entry->pw_uid == geteuid();
}
return false;
}
QString ProvisioningBackend::stateDirectory() const
{
const QByteArray xdgState = qgetenv("XDG_STATE_HOME");
if (!xdgState.isEmpty()) {
const QString candidate = QString::fromLocal8Bit(xdgState);
if (QDir::isAbsolutePath(candidate)) {
return QDir::cleanPath(candidate + QStringLiteral("/nixos-workstations"));
}
}
return QDir::cleanPath(effectiveHomeDirectory()
+ QStringLiteral("/.local/state/nixos-workstations"));
}
QString ProvisioningBackend::diagnosticLogPath() const
{
return stateDirectory() + QStringLiteral("/setup.log");
}
QString ProvisioningBackend::passwordMarker() const
{
return stateDirectory() + QStringLiteral("/password-initialized");
}
QString ProvisioningBackend::pinMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-pin-created");
}
QString ProvisioningBackend::fidoMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-fido-enrolled");
}
QString ProvisioningBackend::yubiKeyEnabledMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-enabled");
}
QString ProvisioningBackend::yubiKeySkippedMarker() const
{
return stateDirectory() + QStringLiteral("/yubikey-skipped");
}
void ProvisioningBackend::refreshState()
{
const bool oldPassword = m_passwordDone;
const bool oldPin = m_pinDone;
const bool oldFido = m_fidoDone;
const bool oldYubiKeyEnabled = m_yubiKeyEnabled;
const bool oldYubiKeySkipped = m_yubiKeySkipped;
m_passwordDone = QFileInfo::exists(passwordMarker());
m_pinDone = QFileInfo::exists(pinMarker());
m_fidoDone = QFileInfo::exists(fidoMarker());
m_yubiKeyEnabled = QFileInfo::exists(yubiKeyEnabledMarker());
m_yubiKeySkipped = QFileInfo::exists(yubiKeySkippedMarker());
if (oldPassword != m_passwordDone
|| oldPin != m_pinDone
|| oldFido != m_fidoDone
|| oldYubiKeyEnabled != m_yubiKeyEnabled
|| oldYubiKeySkipped != m_yubiKeySkipped) {
emit stateChanged();
}
}
bool ProvisioningBackend::ensureStateWritable()
{
const QString dir = stateDirectory();
if (!QDir().mkpath(dir)) {
return false;
}
QFile::setPermissions(dir,
QFileDevice::ReadOwner
| QFileDevice::WriteOwner
| QFileDevice::ExeOwner);
const QString probePath = dir + QStringLiteral("/.write-test");
QFile probe(probePath);
if (!probe.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
const bool ok = probe.write("ok\n") == 3;
probe.close();
probe.remove();
return ok;
}
bool ProvisioningBackend::createMarker(const QString &path)
{
if (!ensureStateWritable()) {
return false;
}
QSaveFile marker(path);
if (!marker.open(QIODevice::WriteOnly)) {
return false;
}
const QByteArray content = QByteArrayLiteral("completed=")
+ QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toUtf8()
+ '\n';
if (marker.write(content) != content.size()) {
marker.cancelWriting();
return false;
}
if (!marker.commit()) {
return false;
}
return QFile::setPermissions(path,
QFileDevice::ReadOwner | QFileDevice::WriteOwner);
}
void ProvisioningBackend::appendLog(const QString &message) const
{
const QString dir = stateDirectory();
if (!QDir().mkpath(dir)) {
return;
}
QFile::setPermissions(dir,
QFileDevice::ReadOwner
| QFileDevice::WriteOwner
| QFileDevice::ExeOwner);
QFile log(diagnosticLogPath());
if (!log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
return;
}
log.write(QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs).toUtf8()
+ QByteArrayLiteral(" ")
+ message.toUtf8()
+ '\n');
log.close();
QFile::setPermissions(diagnosticLogPath(),
QFileDevice::ReadOwner | QFileDevice::WriteOwner);
}
void ProvisioningBackend::setBusy(bool busy)
{
if (m_busy == busy) {
return;
}
m_busy = busy;
emit busyChanged();
}
void ProvisioningBackend::resetFidoInteraction()
{
const bool changed = m_fidoTouchRequired
|| m_fidoTouchRequestCount != 0
|| !m_fidoStatus.isEmpty();
m_fidoTouchRequired = false;
m_fidoTouchRequestCount = 0;
m_fidoStatus.clear();
m_helperStdoutBuffer.clear();
if (changed) {
emit fidoInteractionChanged();
}
}
void ProvisioningBackend::handleHelperStdout(Operation operation, const QByteArray &data)
{
if (operation != Operation::Fido || data.isEmpty()) {
return;
}
m_helperStdoutBuffer += data;
qsizetype newlineIndex = -1;
while ((newlineIndex = m_helperStdoutBuffer.indexOf('\n')) >= 0) {
QByteArray line = m_helperStdoutBuffer.left(newlineIndex);
m_helperStdoutBuffer.remove(0, newlineIndex + 1);
if (!line.isEmpty() && line.endsWith('\r')) {
line.chop(1);
}
bool changed = false;
if (line == QByteArrayLiteral("EVENT_FIDO_INITIALIZING")) {
m_fidoTouchRequired = true;
++m_fidoTouchRequestCount;
m_fidoStatus = QStringLiteral("Touchez la YubiKey dès quelle clignote. Plusieurs touchers peuvent être demandés pendant cette étape. (%1)")
.arg(m_fidoTouchRequestCount);
changed = true;
} else if (line == QByteArrayLiteral("EVENT_FIDO_GENERATING")) {
m_fidoTouchRequired = true;
++m_fidoTouchRequestCount;
m_fidoStatus = QStringLiteral("Touchez de nouveau la YubiKey dès quelle clignote. (%1)")
.arg(m_fidoTouchRequestCount);
changed = true;
} else if (line == QByteArrayLiteral("EVENT_FIDO_UPDATING_HOME")) {
m_fidoTouchRequired = false;
m_fidoStatus = QStringLiteral("Mise à jour de votre espace personnel…");
changed = true;
} else if (line == QByteArrayLiteral("EVENT_FIDO_SYNCHRONIZING")) {
m_fidoTouchRequired = false;
m_fidoStatus = QStringLiteral("Finalisation de la protection…");
changed = true;
} else if (line == QByteArrayLiteral("EVENT_FIDO_TOUCH_REQUIRED")) {
m_fidoTouchRequired = true;
++m_fidoTouchRequestCount;
m_fidoStatus = QStringLiteral("Touchez la YubiKey maintenant. Plusieurs touchers peuvent être demandés pendant cette étape. (%1)")
.arg(m_fidoTouchRequestCount);
changed = true;
}
if (changed) {
emit fidoInteractionChanged();
}
}
}
void ProvisioningBackend::secureClear(QByteArray &data)
{
if (!data.isEmpty()) {
volatile char *p = data.data();
for (qsizetype i = 0; i < data.size(); ++i) {
p[i] = 0;
}
}
data.clear();
data.squeeze();
}
QString ProvisioningBackend::operationName(Operation operation)
{
switch (operation) {
case Operation::Password:
return QStringLiteral("PASSWORD");
case Operation::Pin:
return QStringLiteral("PIN");
case Operation::Fido:
return QStringLiteral("FIDO");
}
return QStringLiteral("UNKNOWN");
}
void ProvisioningBackend::emitResult(Operation operation, bool success, const QString &message)
{
switch (operation) {
case Operation::Password:
emit passwordChangeFinished(success, message);
break;
case Operation::Pin:
emit pinChangeFinished(success, message);
break;
case Operation::Fido:
emit fidoEnrollmentFinished(success, message);
break;
}
}
void ProvisioningBackend::setYubiKeyUsage(bool enabled)
{
if (!authorizedUser()) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
return;
}
if (m_busy) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
// Un compte déjà passé par les étapes YubiKey est considéré comme utilisant
// toujours sa clé. Cela assure la compatibilité avec les états v1.9.0.
if (!enabled && (m_pinDone || m_fidoDone)) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Une YubiKey est déjà configurée pour ce compte."));
return;
}
const QString selectedMarker = enabled ? yubiKeyEnabledMarker() : yubiKeySkippedMarker();
const QString obsoleteMarker = enabled ? yubiKeySkippedMarker() : yubiKeyEnabledMarker();
if (!createMarker(selectedMarker)) {
emit yubiKeyChoiceFinished(false, QStringLiteral("Impossible d'enregistrer votre choix."));
return;
}
QFile::remove(obsoleteMarker);
refreshState();
appendLog(QStringLiteral("YUBIKEY choice=%1").arg(enabled ? QStringLiteral("enabled") : QStringLiteral("skipped")));
emit yubiKeyChoiceFinished(true, QString());
}
void ProvisioningBackend::changePassword(const QString &currentPassword,
const QString &newPassword,
const QString &confirmation)
{
if (!authorizedUser()) {
emit passwordChangeFinished(false, QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
return;
}
if (m_busy) {
emit passwordChangeFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyChoiceMade()) {
emit passwordChangeFinished(false, QStringLiteral("Indiquez d'abord si vous disposez d'une YubiKey."));
return;
}
if (m_passwordDone) {
emit passwordChangeFinished(true, QStringLiteral("Le mot de passe a déjà été personnalisé."));
return;
}
if (currentPassword.isEmpty() || newPassword.isEmpty() || confirmation.isEmpty()) {
emit passwordChangeFinished(false, QStringLiteral("Tous les champs du mot de passe doivent être renseignés."));
return;
}
if (newPassword != confirmation) {
emit passwordChangeFinished(false, QStringLiteral("Les deux nouveaux mots de passe ne correspondent pas."));
return;
}
if (containsLineBreak(currentPassword) || containsLineBreak(newPassword) || containsLineBreak(confirmation)) {
emit passwordChangeFinished(false, QStringLiteral("Le mot de passe contient un caractère non pris en charge."));
return;
}
if (!ensureStateWritable()) {
emit passwordChangeFinished(false, QStringLiteral("Impossible d'enregistrer l'état de configuration dans votre profil."));
return;
}
startHelper(Operation::Password,
QString::fromUtf8(NIXOS_WORKSTATIONS_PASSWORD_HELPER_PATH),
currentPassword,
newPassword,
confirmation);
}
void ProvisioningBackend::initializePin(const QString &newPin,
const QString &confirmation)
{
if (!authorizedUser()) {
emit pinChangeFinished(false, QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
return;
}
if (m_busy) {
emit pinChangeFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyRequested()) {
emit pinChangeFinished(false, QStringLiteral("Aucune YubiKey n'a été sélectionnée pour ce compte."));
return;
}
if (!m_passwordDone) {
emit pinChangeFinished(false, QStringLiteral("Personnalisez d'abord votre mot de passe."));
return;
}
if (m_pinDone) {
emit pinChangeFinished(true, QStringLiteral("Le code PIN de la YubiKey a déjà été initialisé."));
return;
}
if (newPin.size() < 4 || confirmation.size() < 4) {
emit pinChangeFinished(false, QStringLiteral("Le PIN doit contenir au moins 4 caractères."));
return;
}
if (newPin != confirmation) {
emit pinChangeFinished(false, QStringLiteral("Les deux PIN ne correspondent pas."));
return;
}
if (containsLineBreak(newPin) || containsLineBreak(confirmation)) {
emit pinChangeFinished(false, QStringLiteral("Le PIN contient un caractère non pris en charge."));
return;
}
startHelper(Operation::Pin,
QString::fromUtf8(NIXOS_WORKSTATIONS_PIN_HELPER_PATH),
newPin,
confirmation);
}
void ProvisioningBackend::enrollFido(const QString &currentPassword,
const QString &pin)
{
if (!authorizedUser()) {
emit fidoEnrollmentFinished(false, QStringLiteral("Cette opération n'est pas autorisée pour cet utilisateur."));
return;
}
if (m_busy) {
emit fidoEnrollmentFinished(false, QStringLiteral("Une opération de configuration est déjà en cours."));
return;
}
if (!yubiKeyRequested()) {
emit fidoEnrollmentFinished(false, QStringLiteral("Aucune YubiKey n'a été sélectionnée pour ce compte."));
return;
}
if (!m_passwordDone || !m_pinDone) {
emit fidoEnrollmentFinished(false, QStringLiteral("Le mot de passe et le code PIN doivent être configurés avant d'associer la YubiKey."));
return;
}
if (m_fidoDone) {
emit fidoEnrollmentFinished(true, QStringLiteral("La YubiKey est déjà associée à votre espace personnel."));
return;
}
if (currentPassword.isEmpty() || pin.size() < 4) {
emit fidoEnrollmentFinished(false, QStringLiteral("Saisissez le nouveau mot de passe choisi à l’étape 1 et le code PIN de la YubiKey."));
return;
}
if (containsLineBreak(currentPassword) || containsLineBreak(pin)) {
emit fidoEnrollmentFinished(false, QStringLiteral("Un des secrets contient un caractère non pris en charge."));
return;
}
resetFidoInteraction();
m_fidoStatus = QStringLiteral("Préparation de l'association de la YubiKey…");
emit fidoInteractionChanged();
startHelper(Operation::Fido,
QString::fromUtf8(NIXOS_WORKSTATIONS_FIDO_HELPER_PATH),
currentPassword,
pin);
}
void ProvisioningBackend::startHelper(Operation operation,
const QString &helperPath,
const QString &firstSecret,
const QString &secondSecret,
const QString &thirdSecret)
{
const QFileInfo helperInfo(helperPath);
if (!helperInfo.exists() || !helperInfo.isFile() || !helperInfo.isExecutable()) {
appendLog(operationName(operation) + QStringLiteral(" failed: helper unavailable"));
emitResult(operation, false, QStringLiteral("Le composant système requis est introuvable ou inutilisable."));
return;
}
QByteArray first = firstSecret.toUtf8();
QByteArray second = secondSecret.toUtf8();
QByteArray third = thirdSecret.toUtf8();
m_pendingPayload.clear();
m_pendingPayload.reserve(first.size() + second.size() + third.size() + 3);
m_pendingPayload += first;
m_pendingPayload += '\n';
m_pendingPayload += second;
m_pendingPayload += '\n';
m_pendingPayload += third;
m_pendingPayload += '\n';
secureClear(first);
secureClear(second);
secureClear(third);
m_helperTimedOut = false;
m_helperStdoutBuffer.clear();
setBusy(true);
auto *process = new QProcess(this);
m_process = process;
process->setProgram(helperPath);
process->setProcessChannelMode(QProcess::SeparateChannels);
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("LC_ALL"), QStringLiteral("C"));
env.insert(QStringLiteral("LANG"), QStringLiteral("C"));
process->setProcessEnvironment(env);
auto *timer = new QTimer(process);
timer->setSingleShot(true);
timer->setInterval(kHelperTimeoutMs);
connect(timer, &QTimer::timeout, process, [this, process, operation]() {
if (m_process != process || process->state() == QProcess::NotRunning) {
return;
}
m_helperTimedOut = true;
appendLog(operationName(operation) + QStringLiteral(" timeout"));
process->kill();
});
connect(process, &QProcess::started, this, [this, process, timer, operation]() {
if (m_process != process) {
return;
}
process->write(m_pendingPayload);
process->closeWriteChannel();
secureClear(m_pendingPayload);
appendLog(operationName(operation) + QStringLiteral(" helper started"));
timer->start();
});
connect(process, &QProcess::readyReadStandardOutput, this,
[this, process, operation]() {
if (m_process != process) {
return;
}
handleHelperStdout(operation, process->readAllStandardOutput());
});
connect(process, &QProcess::errorOccurred, this,
[this, process, timer, operation](QProcess::ProcessError error) {
if (m_process != process || error != QProcess::FailedToStart) {
return;
}
timer->stop();
secureClear(m_pendingPayload);
m_process = nullptr;
process->deleteLater();
setBusy(false);
appendLog(operationName(operation) + QStringLiteral(" failed to start"));
if (operation == Operation::Fido) {
resetFidoInteraction();
}
emitResult(operation, false, QStringLiteral("Impossible de démarrer le composant de configuration."));
});
connect(process,
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
this,
[this, process, timer, operation](int exitCode, QProcess::ExitStatus exitStatus) {
if (m_process != process) {
return;
}
timer->stop();
secureClear(m_pendingPayload);
QByteArray stderrData = process->readAllStandardError();
QByteArray stdoutData = process->readAllStandardOutput();
if (operation == Operation::Fido && !stdoutData.isEmpty()) {
handleHelperStdout(operation, stdoutData);
stdoutData.clear();
}
QByteArray combined = stderrData + '\n' + stdoutData;
const bool timedOut = m_helperTimedOut;
const bool helperSucceeded = !timedOut
&& exitStatus == QProcess::NormalExit
&& exitCode == 0;
bool markerCreated = false;
QString message;
if (helperSucceeded) {
switch (operation) {
case Operation::Password:
markerCreated = createMarker(passwordMarker());
message = markerCreated
? QStringLiteral("Votre nouveau mot de passe protège maintenant votre espace personnel.")
: QStringLiteral("Le mot de passe a été modifié mais l'état local n'a pas pu être enregistré.");
break;
case Operation::Pin:
markerCreated = createMarker(pinMarker());
message = markerCreated
? QStringLiteral("Le code PIN de votre YubiKey est maintenant initialisé.")
: QStringLiteral("Le PIN a été créé mais l'état local n'a pas pu être enregistré.");
break;
case Operation::Fido:
markerCreated = createMarker(fidoMarker());
message = markerCreated
? QStringLiteral("Votre YubiKey peut maintenant ouvrir votre espace personnel chiffré.")
: QStringLiteral("La YubiKey a été associée mais l'état local n'a pas pu être enregistré.");
break;
}
} else {
message = safeMessageForFailure(operation, exitCode, combined, timedOut);
}
QString token = QString::fromUtf8(combined).trimmed();
token.remove(QRegularExpression(QStringLiteral("[^A-Za-z0-9_\\-]")));
if (token.size() > 80) token.truncate(80);
if (token.isEmpty()) token = QStringLiteral("none");
appendLog(operationName(operation)
+ QStringLiteral(" helper finished exitCode=%1 timeout=%2 marker=%3 token=%4")
.arg(exitCode)
.arg(timedOut ? QStringLiteral("yes") : QStringLiteral("no"))
.arg(markerCreated ? QStringLiteral("yes") : QStringLiteral("no"))
.arg(token));
secureClear(stderrData);
secureClear(stdoutData);
secureClear(combined);
m_process = nullptr;
process->deleteLater();
setBusy(false);
refreshState();
if (operation == Operation::Fido) {
m_fidoTouchRequired = false;
m_fidoStatus = helperSucceeded && markerCreated
? QStringLiteral("Association de la YubiKey terminée.")
: QStringLiteral("Association de la YubiKey interrompue.");
emit fidoInteractionChanged();
}
emitResult(operation, helperSucceeded && markerCreated, message);
});
process->start();
}
QString ProvisioningBackend::safeMessageForFailure(Operation operation,
int exitCode,
const QByteArray &outputData,
bool timedOut) const
{
const QString output = QString::fromUtf8(outputData).toLower();
if (timedOut || exitCode == 124 || output.contains(QStringLiteral("timeout"))) {
if (operation == Operation::Fido) {
return QStringLiteral("L'association de la YubiKey a expiré. Laissez-la branchée et recommencez l'opération.");
}
return QStringLiteral("L'opération a expiré avant de pouvoir être terminée.");
}
if (operation == Operation::Password) {
if (output.contains(QStringLiteral("current_rejected"))) {
return QStringLiteral("Le mot de passe temporaire actuel est incorrect.");
}
if (output.contains(QStringLiteral("new_rejected"))) {
return QStringLiteral("Le nouveau mot de passe a été refusé par la politique de sécurité.");
}
return QStringLiteral("Le mot de passe n'a pas pu être modifié.");
}
if (operation == Operation::Pin) {
if (output.contains(QStringLiteral("pin_already_configured"))) {
return QStringLiteral("Cette YubiKey possède déjà un code PIN. Utilisez une YubiKey vierge ou réinitialisée.");
}
if (output.contains(QStringLiteral("pin_auth_blocked"))) {
return QStringLiteral("Les opérations PIN sont temporairement bloquées. Débranchez puis rebranchez la YubiKey.");
}
if (output.contains(QStringLiteral("pin_blocked"))) {
return QStringLiteral("Le code PIN est bloqué. La YubiKey doit être réinitialisée avant de recommencer.");
}
if (output.contains(QStringLiteral("multiple_yubikey"))) {
return QStringLiteral("Plusieurs clés de sécurité sont détectées. Ne laissez branchée que votre YubiKey.");
}
if (output.contains(QStringLiteral("no_yubikey"))) {
return QStringLiteral("Aucune YubiKey compatible n'a été détectée.");
}
if (output.contains(QStringLiteral("pin_policy"))) {
return QStringLiteral("Le code PIN choisi n'est pas accepté par la YubiKey.");
}
return QStringLiteral("Le code PIN de la YubiKey n'a pas pu être initialisé.");
}
if (output.contains(QStringLiteral("fido_bad_password"))) {
return QStringLiteral("Le mot de passe fourni est incorrect.");
}
if (output.contains(QStringLiteral("fido_bad_pin"))) {
return QStringLiteral("Le code PIN de la YubiKey est incorrect. Vérifiez-le avant une nouvelle tentative.");
}
if (output.contains(QStringLiteral("multiple_yubikey"))) {
return QStringLiteral("Plusieurs clés de sécurité sont détectées. Ne laissez branchée que votre YubiKey.");
}
if (output.contains(QStringLiteral("no_yubikey"))) {
return QStringLiteral("Aucune YubiKey compatible n'a été détectée.");
}
if (output.contains(QStringLiteral("fido_enroll_failed"))) {
return QStringLiteral("La YubiKey n'a pas pu être associée à votre espace personnel. Vérifiez le mot de passe et le code PIN.");
}
return QStringLiteral("L'association de la YubiKey à votre espace personnel a échoué.");
}