File indexing completed on 2024-05-12 05:52:47

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 #include "account/keys.h"
0006 
0007 #include "../../secrets/test-utils/random.h"
0008 #include "../../test-utils/spy.h"
0009 
0010 #include <QSignalSpy>
0011 #include <QTest>
0012 
0013 #include <cstring>
0014 
0015 static QByteArray fill(int size)
0016 {
0017     QByteArray a;
0018     a.resize(size);
0019     /*
0020      * Because this value is used to generate the expected challenge value(s) up front, this salt has to match the
0021      * behaviour of  test::fakeRandom() in case of 'new' passwords where the actual salt will be drawn 'randomly'
0022      * (from test::fakeRandom()).
0023      */
0024     a.fill('A', -1);
0025     return a;
0026 }
0027 
0028 static QByteArray salt()
0029 {
0030     return fill(crypto_pwhash_SALTBYTES);
0031 }
0032 
0033 static QByteArray masterPassword(void)
0034 {
0035     static QByteArray MASTER_PASSWORD("hello, world");
0036     return MASTER_PASSWORD;
0037 }
0038 
0039 static secrets::SecureMemory * secret(void)
0040 {
0041     const auto master = masterPassword();
0042     size_t size = (size_t) master.size();
0043     auto memory = secrets::SecureMemory::allocate(size);
0044     if (memory) {
0045         std::memcpy(memory->data(), master.constData(), size);
0046     }
0047     return memory;
0048 }
0049 
0050 static std::optional<secrets::KeyDerivationParameters> keyParams = secrets::KeyDerivationParameters::create(
0051     crypto_secretbox_KEYBYTES, crypto_pwhash_ALG_DEFAULT, crypto_pwhash_MEMLIMIT_MIN, crypto_pwhash_OPSLIMIT_MIN
0052 );
0053 
0054 static secrets::SecureMasterKey * key(secrets::SecureMemory *password)
0055 {
0056     if (!keyParams) {
0057         qDebug() << "Unable to setup() dummy master key to generate test data with";
0058         return nullptr;
0059     }
0060     return secrets::SecureMasterKey::derive(password, *keyParams, salt(), &test::fakeRandom);
0061 }
0062 
0063 static std::optional<secrets::EncryptedSecret> challenge(void)
0064 {
0065     QScopedPointer<secrets::SecureMemory> s(secret());
0066     if (!s) {
0067         qDebug() << "Unable to generate challenge(), unable to allocate buffer for password secret.";
0068         return std::nullopt;
0069     }
0070 
0071     QScopedPointer<secrets::SecureMasterKey> k(key(s.data()));
0072     if (!k) {
0073         qDebug() << "Unable to generate challenge(), unable to setup dummy master key.";
0074         return std::nullopt;
0075     }
0076     return k->encrypt(s.data());
0077 }
0078 
0079 class PasswordFlowTest : public QObject // clazy:exclude=ctor-missing-parent-argument
0080 {
0081     Q_OBJECT
0082 private Q_SLOTS:
0083     void initTestCase(void);
0084     void supplyExistingPassword(void);
0085     void cancelExistingPassword(void);
0086     void supplyNewPassword(void);
0087     void cancelNewPassword(void);
0088     void retryExistingPassword(void);
0089 private:
0090     QByteArray m_salt = salt();
0091     std::optional<secrets::EncryptedSecret> m_challenge = challenge();
0092 };
0093 
0094 void PasswordFlowTest::initTestCase(void)
0095 {
0096     QVERIFY2(keyParams, "should be able to construct key derivation parameters");
0097     QVERIFY2(m_challenge, "should be able to construct password challenge");
0098     qDebug() << "Running with challenge:" << m_challenge->cryptText().toBase64() << "nonce:" << m_challenge->nonce().toBase64();
0099 }
0100 
0101 void PasswordFlowTest::supplyNewPassword(void)
0102 {
0103     accounts::AccountSecret uut(&test::fakeRandom);
0104     QSignalSpy existingPasswordNeeded(&uut, &accounts::AccountSecret::existingPasswordNeeded);
0105     QSignalSpy newPasswordNeeded(&uut, &accounts::AccountSecret::newPasswordNeeded);
0106     QSignalSpy passwordAvailable(&uut, &accounts::AccountSecret::passwordAvailable);
0107     QSignalSpy requestsCancelled(&uut, &accounts::AccountSecret::requestsCancelled);
0108     QSignalSpy keyAvailable(&uut, &accounts::AccountSecret::keyAvailable);
0109     QSignalSpy keyFailed(&uut, &accounts::AccountSecret::keyFailed);
0110 
0111     // check correct initial state is reported
0112     QCOMPARE(uut.isStillAlive(), true);
0113     QCOMPARE(uut.isNewPasswordRequested(), false);
0114     QCOMPARE(uut.isExistingPasswordRequested(), false);
0115     QCOMPARE(uut.isPasswordAvailable(), false);
0116     QCOMPARE(uut.isKeyAvailable(), false);
0117     QCOMPARE(uut.key(), nullptr);
0118     QVERIFY2(!uut.challenge(), "should not have a (generated) password challenge yet");
0119 
0120     // advance the state: request password
0121     QVERIFY2(uut.requestNewPassword(), "should be able to request a (new) password");
0122     QVERIFY2(test::signal_eventually_emitted_once(newPasswordNeeded), "request for (new) password should be signalled");
0123 
0124     // check the state is correctly updated
0125     QCOMPARE(uut.isStillAlive(), true);
0126     QCOMPARE(uut.isNewPasswordRequested(), true);
0127     QCOMPARE(uut.isExistingPasswordRequested(), false);
0128     QCOMPARE(uut.isPasswordAvailable(), false);
0129     QCOMPARE(uut.isKeyAvailable(), false);
0130     QCOMPARE(uut.key(), nullptr);
0131     QVERIFY2(!uut.challenge(), "should not have a (generated) password challenge yet");
0132 
0133     QCOMPARE(newPasswordNeeded.count(), 1);
0134     QCOMPARE(passwordAvailable.count(), 0);
0135     QCOMPARE(existingPasswordNeeded.count(), 0);
0136     QCOMPARE(keyAvailable.count(), 0);
0137     QCOMPARE(keyFailed.count(), 0);
0138     QCOMPARE(requestsCancelled.count(), 0);
0139 
0140     // advance the state: supply password
0141     QString password = QString::fromUtf8(masterPassword());
0142     QString wiped = QStringLiteral("*").repeated(password.size());
0143 
0144     QVERIFY2(uut.answerNewPassword(password, *keyParams), "(new) password should be accepted");
0145     QVERIFY2(test::signal_eventually_emitted_once(passwordAvailable), "availability of the (new) password should be signalled");
0146     QCOMPARE(password, wiped);
0147 
0148     // check the state is correctly updated
0149     QCOMPARE(uut.isStillAlive(), true);
0150     QCOMPARE(uut.isNewPasswordRequested(), true);
0151     QCOMPARE(uut.isExistingPasswordRequested(), false);
0152     QCOMPARE(uut.isPasswordAvailable(), true);
0153     QCOMPARE(uut.isKeyAvailable(), false);
0154     QCOMPARE(uut.key(), nullptr);
0155     QVERIFY2(!uut.challenge(), "should still not have a (generated) password challenge yet");
0156 
0157     QCOMPARE(newPasswordNeeded.count(), 1);
0158     QCOMPARE(passwordAvailable.count(), 1);
0159     QCOMPARE(existingPasswordNeeded.count(), 0);
0160     QCOMPARE(keyAvailable.count(), 0);
0161     QCOMPARE(keyFailed.count(), 0);
0162     QCOMPARE(requestsCancelled.count(), 0);
0163 
0164     // advance the state: derive the master key
0165     QVERIFY2(uut.deriveKey(), "key derivation should succeed");
0166     QVERIFY2(test::signal_eventually_emitted_once(keyAvailable), "availability of the master key should be signalled");
0167 
0168     // check the state is correctly updated
0169     QCOMPARE(uut.isStillAlive(), true);
0170     QCOMPARE(uut.isNewPasswordRequested(), true);
0171     QCOMPARE(uut.isExistingPasswordRequested(), false);
0172     QCOMPARE(uut.isPasswordAvailable(), false);
0173     QCOMPARE(uut.isKeyAvailable(), true);
0174     QVERIFY2(uut.key(), "should have a master key by now");
0175     const auto generatedChallenge = uut.challenge();
0176     QVERIFY2(generatedChallenge, "should have a (generated) password challenge by now");
0177     QCOMPARE(generatedChallenge->cryptText(), m_challenge->cryptText());
0178     QCOMPARE(generatedChallenge->nonce(), m_challenge->nonce());
0179 
0180     QCOMPARE(newPasswordNeeded.count(), 1);
0181     QCOMPARE(passwordAvailable.count(), 1);
0182     QCOMPARE(existingPasswordNeeded.count(), 0);
0183     QCOMPARE(keyAvailable.count(), 1);
0184     QCOMPARE(keyFailed.count(), 0);
0185     QCOMPARE(requestsCancelled.count(), 0);
0186 }
0187 
0188 void PasswordFlowTest::cancelNewPassword(void)
0189 {
0190     accounts::AccountSecret uut(&test::fakeRandom);
0191     QSignalSpy existingPasswordNeeded(&uut, &accounts::AccountSecret::existingPasswordNeeded);
0192     QSignalSpy newPasswordNeeded(&uut, &accounts::AccountSecret::newPasswordNeeded);
0193     QSignalSpy passwordAvailable(&uut, &accounts::AccountSecret::passwordAvailable);
0194     QSignalSpy requestsCancelled(&uut, &accounts::AccountSecret::requestsCancelled);
0195     QSignalSpy keyAvailable(&uut, &accounts::AccountSecret::keyAvailable);
0196     QSignalSpy keyFailed(&uut, &accounts::AccountSecret::keyFailed);
0197 
0198     // check correct initial state is reported
0199     QCOMPARE(uut.isStillAlive(), true);
0200     QCOMPARE(uut.isNewPasswordRequested(), false);
0201     QCOMPARE(uut.isExistingPasswordRequested(), false);
0202     QCOMPARE(uut.isPasswordAvailable(), false);
0203     QCOMPARE(uut.isKeyAvailable(), false);
0204     QCOMPARE(uut.key(), nullptr);
0205     QVERIFY2(!uut.challenge(), "should not have a (generated) password challenge yet");
0206 
0207     // advance the state: request password
0208     QVERIFY2(uut.requestNewPassword(), "should be able to request a (new) password");
0209     QVERIFY2(test::signal_eventually_emitted_once(newPasswordNeeded), "request for (new) password should be signalled");
0210 
0211     // check the state is correctly updated
0212     QCOMPARE(uut.isStillAlive(), true);
0213     QCOMPARE(uut.isNewPasswordRequested(), true);
0214     QCOMPARE(uut.isExistingPasswordRequested(), false);
0215     QCOMPARE(uut.isPasswordAvailable(), false);
0216     QCOMPARE(uut.isKeyAvailable(), false);
0217     QCOMPARE(uut.key(), nullptr);
0218     QVERIFY2(!uut.challenge(), "should still not have a (generated) password challenge yet");
0219 
0220     QCOMPARE(newPasswordNeeded.count(), 1);
0221     QCOMPARE(passwordAvailable.count(), 0);
0222     QCOMPARE(existingPasswordNeeded.count(), 0);
0223     QCOMPARE(keyAvailable.count(), 0);
0224     QCOMPARE(keyFailed.count(), 0);
0225     QCOMPARE(requestsCancelled.count(), 0);
0226 
0227     // advance the state: cancel the request
0228     uut.cancelRequests();
0229     QVERIFY2(test::signal_eventually_emitted_once(requestsCancelled), "requests for (new) password should be cancelled by now");
0230 
0231     // check the state is correctly updated
0232     QCOMPARE(uut.isStillAlive(), false);
0233     QCOMPARE(uut.isNewPasswordRequested(), true);
0234     QCOMPARE(uut.isExistingPasswordRequested(), false);
0235     QCOMPARE(uut.isPasswordAvailable(), false);
0236     QCOMPARE(uut.isKeyAvailable(), false);
0237     QCOMPARE(uut.key(), nullptr);
0238     QVERIFY2(!uut.challenge(), "should still not acknowledge a (generated) password challenge");
0239 
0240     QCOMPARE(newPasswordNeeded.count(), 1);
0241     QCOMPARE(passwordAvailable.count(), 0);
0242     QCOMPARE(existingPasswordNeeded.count(), 0);
0243     QCOMPARE(keyAvailable.count(), 0);
0244     QCOMPARE(keyFailed.count(), 0);
0245     QCOMPARE(requestsCancelled.count(), 1);
0246 }
0247 
0248 void PasswordFlowTest::cancelExistingPassword(void)
0249 {
0250     accounts::AccountSecret uut;
0251     QSignalSpy existingPasswordNeeded(&uut, &accounts::AccountSecret::existingPasswordNeeded);
0252     QSignalSpy newPasswordNeeded(&uut, &accounts::AccountSecret::newPasswordNeeded);
0253     QSignalSpy passwordAvailable(&uut, &accounts::AccountSecret::passwordAvailable);
0254     QSignalSpy requestsCancelled(&uut, &accounts::AccountSecret::requestsCancelled);
0255     QSignalSpy keyAvailable(&uut, &accounts::AccountSecret::keyAvailable);
0256     QSignalSpy keyFailed(&uut, &accounts::AccountSecret::keyFailed);
0257 
0258     // check correct initial state is reported
0259     QCOMPARE(uut.isStillAlive(), true);
0260     QCOMPARE(uut.isNewPasswordRequested(), false);
0261     QCOMPARE(uut.isExistingPasswordRequested(), false);
0262     QCOMPARE(uut.isPasswordAvailable(), false);
0263     QCOMPARE(uut.isKeyAvailable(), false);
0264     QCOMPARE(uut.key(), nullptr);
0265     QVERIFY2(!uut.challenge(), "should not have a password challenge yet");
0266 
0267     // advance the state: request password
0268     QVERIFY2(uut.requestExistingPassword(*m_challenge, m_salt, *keyParams), "should be able to request a (existing) password");
0269     QVERIFY2(test::signal_eventually_emitted_once(existingPasswordNeeded), "request for (existing) password should be signalled");
0270 
0271     // check the state is correctly updated
0272     QCOMPARE(uut.isStillAlive(), true);
0273     QCOMPARE(uut.isNewPasswordRequested(), false);
0274     QCOMPARE(uut.isExistingPasswordRequested(), true);
0275     QCOMPARE(uut.isPasswordAvailable(), false);
0276     QCOMPARE(uut.isKeyAvailable(), false);
0277     QCOMPARE(uut.key(), nullptr);
0278     const auto preservedChallenge = uut.challenge();
0279     QVERIFY2(preservedChallenge, "should have the supplied password challenge by now");
0280     QCOMPARE(preservedChallenge->cryptText(), m_challenge->cryptText());
0281     QCOMPARE(preservedChallenge->nonce(), m_challenge->nonce());
0282 
0283     QCOMPARE(newPasswordNeeded.count(), 0);
0284     QCOMPARE(passwordAvailable.count(), 0);
0285     QCOMPARE(existingPasswordNeeded.count(), 1);
0286     QCOMPARE(keyAvailable.count(), 0);
0287     QCOMPARE(keyFailed.count(), 0);
0288     QCOMPARE(requestsCancelled.count(), 0);
0289 
0290     // advance the state: cancel the request
0291     uut.cancelRequests();
0292     QVERIFY2(test::signal_eventually_emitted_once(requestsCancelled), "requests for (new) password should be cancelled by now");
0293 
0294     // check the state is correctly updated
0295     QCOMPARE(uut.isStillAlive(), false);
0296     QCOMPARE(uut.isNewPasswordRequested(), false);
0297     QCOMPARE(uut.isExistingPasswordRequested(), true);
0298     QCOMPARE(uut.isPasswordAvailable(), false);
0299     QCOMPARE(uut.isKeyAvailable(), false);
0300     QCOMPARE(uut.key(), nullptr);
0301     QVERIFY2(!uut.challenge(), "should no longer acknowledge to the supplied password challenge");
0302 
0303     QCOMPARE(newPasswordNeeded.count(), 0);
0304     QCOMPARE(passwordAvailable.count(), 0);
0305     QCOMPARE(existingPasswordNeeded.count(), 1);
0306     QCOMPARE(keyAvailable.count(), 0);
0307     QCOMPARE(keyFailed.count(), 0);
0308     QCOMPARE(requestsCancelled.count(), 1);
0309 }
0310 
0311 void PasswordFlowTest::supplyExistingPassword(void)
0312 {
0313     accounts::AccountSecret uut;
0314     QSignalSpy existingPasswordNeeded(&uut, &accounts::AccountSecret::existingPasswordNeeded);
0315     QSignalSpy newPasswordNeeded(&uut, &accounts::AccountSecret::newPasswordNeeded);
0316     QSignalSpy passwordAvailable(&uut, &accounts::AccountSecret::passwordAvailable);
0317     QSignalSpy requestsCancelled(&uut, &accounts::AccountSecret::requestsCancelled);
0318     QSignalSpy keyAvailable(&uut, &accounts::AccountSecret::keyAvailable);
0319     QSignalSpy keyFailed(&uut, &accounts::AccountSecret::keyFailed);
0320 
0321     // check correct initial state is reported
0322     QCOMPARE(uut.isStillAlive(), true);
0323     QCOMPARE(uut.isNewPasswordRequested(), false);
0324     QCOMPARE(uut.isExistingPasswordRequested(), false);
0325     QCOMPARE(uut.isPasswordAvailable(), false);
0326     QCOMPARE(uut.isKeyAvailable(), false);
0327     QCOMPARE(uut.key(), nullptr);
0328     QVERIFY2(!uut.challenge(), "should not have a password challenge yet");
0329 
0330     // advance the state: request password
0331     QVERIFY2(uut.requestExistingPassword(*m_challenge, m_salt, *keyParams), "should be able to request a (existing) password");
0332     QVERIFY2(test::signal_eventually_emitted_once(existingPasswordNeeded), "request for (existing) password should be signalled");
0333     const auto suppliedChallenge = uut.challenge();
0334     QVERIFY2(suppliedChallenge, "should have the supplied password challenge by now");
0335     QCOMPARE(suppliedChallenge->cryptText(), m_challenge->cryptText());
0336     QCOMPARE(suppliedChallenge->nonce(), m_challenge->nonce());
0337 
0338     // check the state is correctly updated
0339     QCOMPARE(uut.isStillAlive(), true);
0340     QCOMPARE(uut.isNewPasswordRequested(), false);
0341     QCOMPARE(uut.isExistingPasswordRequested(), true);
0342     QCOMPARE(uut.isPasswordAvailable(), false);
0343     QCOMPARE(uut.isKeyAvailable(), false);
0344     QCOMPARE(uut.key(), nullptr);
0345 
0346     QCOMPARE(newPasswordNeeded.count(), 0);
0347     QCOMPARE(passwordAvailable.count(), 0);
0348     QCOMPARE(existingPasswordNeeded.count(), 1);
0349     QCOMPARE(keyAvailable.count(), 0);
0350     QCOMPARE(keyFailed.count(), 0);
0351     QCOMPARE(requestsCancelled.count(), 0);
0352 
0353     // advance the state: supply password
0354     QString password = QString::fromUtf8(masterPassword());
0355     QString wiped = QStringLiteral("*").repeated(password.size());
0356 
0357     QVERIFY2(uut.answerExistingPassword(password), "(existing) password should be accepted");
0358     QVERIFY2(test::signal_eventually_emitted_once(passwordAvailable), "availability of the (existing) password should be signalled");
0359     QCOMPARE(password, wiped);
0360 
0361     // check the state is correctly updated
0362     QCOMPARE(uut.isStillAlive(), true);
0363     QCOMPARE(uut.isNewPasswordRequested(), false);
0364     QCOMPARE(uut.isExistingPasswordRequested(), true);
0365     QCOMPARE(uut.isPasswordAvailable(), true);
0366     QCOMPARE(uut.isKeyAvailable(), false);
0367     QCOMPARE(uut.key(), nullptr);
0368     const auto preservedChallenge = uut.challenge();
0369     QVERIFY2(preservedChallenge, "should still have the same supplied password challenge after answering with a password");
0370     QCOMPARE(preservedChallenge->cryptText(), m_challenge->cryptText());
0371     QCOMPARE(preservedChallenge->nonce(), m_challenge->nonce());
0372 
0373     QCOMPARE(newPasswordNeeded.count(), 0);
0374     QCOMPARE(passwordAvailable.count(), 1);
0375     QCOMPARE(existingPasswordNeeded.count(), 1);
0376     QCOMPARE(keyAvailable.count(), 0);
0377     QCOMPARE(keyFailed.count(), 0);
0378     QCOMPARE(requestsCancelled.count(), 0);
0379 
0380     // advance the state: derive the master key
0381     QVERIFY2(uut.deriveKey(), "key derivation should succeed");
0382     QVERIFY2(test::signal_eventually_emitted_once(keyAvailable), "availability of the master key should be signalled");
0383 
0384     // check the state is correctly updated
0385     QCOMPARE(uut.isStillAlive(), true);
0386     QCOMPARE(uut.isNewPasswordRequested(), false);
0387     QCOMPARE(uut.isExistingPasswordRequested(), true);
0388     QCOMPARE(uut.isPasswordAvailable(), false);
0389     QCOMPARE(uut.isKeyAvailable(), true);
0390     QVERIFY2(uut.key(), "should have a master key by now");
0391     const auto finalChallenge = uut.challenge();
0392     QVERIFY2(finalChallenge, "should still have the same supplied password challenge after key derivation");
0393     QCOMPARE(finalChallenge->cryptText(), m_challenge->cryptText());
0394     QCOMPARE(finalChallenge->nonce(), m_challenge->nonce());
0395 
0396     QCOMPARE(newPasswordNeeded.count(), 0);
0397     QCOMPARE(passwordAvailable.count(), 1);
0398     QCOMPARE(existingPasswordNeeded.count(), 1);
0399     QCOMPARE(keyAvailable.count(), 1);
0400     QCOMPARE(keyFailed.count(), 0);
0401     QCOMPARE(requestsCancelled.count(), 0);
0402 }
0403 
0404 void PasswordFlowTest::retryExistingPassword(void)
0405 {
0406     accounts::AccountSecret uut;
0407     QSignalSpy existingPasswordNeeded(&uut, &accounts::AccountSecret::existingPasswordNeeded);
0408     QSignalSpy newPasswordNeeded(&uut, &accounts::AccountSecret::newPasswordNeeded);
0409     QSignalSpy passwordAvailable(&uut, &accounts::AccountSecret::passwordAvailable);
0410     QSignalSpy requestsCancelled(&uut, &accounts::AccountSecret::requestsCancelled);
0411     QSignalSpy keyAvailable(&uut, &accounts::AccountSecret::keyAvailable);
0412     QSignalSpy keyFailed(&uut, &accounts::AccountSecret::keyFailed);
0413 
0414     // check correct initial state is reported
0415     QCOMPARE(uut.isStillAlive(), true);
0416     QCOMPARE(uut.isNewPasswordRequested(), false);
0417     QCOMPARE(uut.isExistingPasswordRequested(), false);
0418     QCOMPARE(uut.isPasswordAvailable(), false);
0419     QCOMPARE(uut.isKeyAvailable(), false);
0420     QCOMPARE(uut.key(), nullptr);
0421     QVERIFY2(!uut.challenge(), "should not have a password challenge yet");
0422 
0423     // advance the state: request password
0424     QVERIFY2(uut.requestExistingPassword(*m_challenge, m_salt, *keyParams), "should be able to request a (existing) password");
0425     QVERIFY2(test::signal_eventually_emitted_once(existingPasswordNeeded), "request for (existing) password should be signalled");
0426     const auto suppliedChallenge = uut.challenge();
0427     QVERIFY2(suppliedChallenge, "should have the supplied password challenge by now");
0428     QCOMPARE(suppliedChallenge->cryptText(), m_challenge->cryptText());
0429     QCOMPARE(suppliedChallenge->nonce(), m_challenge->nonce());
0430 
0431     // check the state is correctly updated
0432     QCOMPARE(uut.isStillAlive(), true);
0433     QCOMPARE(uut.isNewPasswordRequested(), false);
0434     QCOMPARE(uut.isExistingPasswordRequested(), true);
0435     QCOMPARE(uut.isPasswordAvailable(), false);
0436     QCOMPARE(uut.isKeyAvailable(), false);
0437     QCOMPARE(uut.key(), nullptr);
0438 
0439     QCOMPARE(newPasswordNeeded.count(), 0);
0440     QCOMPARE(passwordAvailable.count(), 0);
0441     QCOMPARE(existingPasswordNeeded.count(), 1);
0442     QCOMPARE(keyAvailable.count(), 0);
0443     QCOMPARE(keyFailed.count(), 0);
0444     QCOMPARE(requestsCancelled.count(), 0);
0445 
0446     // advance the state: supply wrong password
0447     QString wrongPassword(QStringLiteral("wrong"));
0448     QString wipedWrongPassword = QStringLiteral("*").repeated(wrongPassword.size());
0449 
0450     QVERIFY2(uut.answerExistingPassword(wrongPassword), "password attempt should be accepted");
0451     QVERIFY2(test::signal_eventually_emitted_once(passwordAvailable), "availability of an attempt should be signalled");
0452     QCOMPARE(wrongPassword, wipedWrongPassword);
0453 
0454     // advance the state: attempt to derive the master key
0455     QVERIFY2(!uut.deriveKey(), "key derivation should fail on wrong password");
0456     QVERIFY2(test::signal_eventually_emitted_once(keyFailed), "failure to derive the master key should be signalled");
0457 
0458     // check the state is correctly updated
0459     QCOMPARE(uut.isStillAlive(), true);
0460     QCOMPARE(uut.isNewPasswordRequested(), false);
0461     QCOMPARE(uut.isExistingPasswordRequested(), true);
0462     QCOMPARE(uut.isPasswordAvailable(), false);
0463     QCOMPARE(uut.isKeyAvailable(), false);
0464     QCOMPARE(uut.key(), nullptr);
0465     const auto stillPreservedChallenge = uut.challenge();
0466     QVERIFY2(stillPreservedChallenge, "should still have the same supplied password challenge after answering with a password");
0467     QCOMPARE(stillPreservedChallenge->cryptText(), m_challenge->cryptText());
0468     QCOMPARE(stillPreservedChallenge->nonce(), m_challenge->nonce());
0469 
0470     QCOMPARE(newPasswordNeeded.count(), 0);
0471     QCOMPARE(passwordAvailable.count(), 1);
0472     QCOMPARE(existingPasswordNeeded.count(), 1);
0473     QCOMPARE(keyAvailable.count(), 0);
0474     QCOMPARE(requestsCancelled.count(), 0);
0475 
0476     // advance the state: supply correct password
0477     QString correctPassword = QString::fromUtf8(masterPassword());
0478     QString wipedCorrectPassword = QStringLiteral("*").repeated(correctPassword.size());
0479 
0480     QVERIFY2(uut.answerExistingPassword(correctPassword), "(existing) password should be accepted");
0481     QVERIFY2(test::signal_eventually_emitted_twice(passwordAvailable), "availability of the (existing) password should be signalled");
0482     QCOMPARE(correctPassword, wipedCorrectPassword);
0483 
0484     // advance the state: attempt to derive the master key
0485     QVERIFY2(uut.deriveKey(), "key derivation should succeed");
0486     QVERIFY2(test::signal_eventually_emitted_once(keyAvailable), "availability of the master key should be signalled");
0487 
0488     // check the state is correctly updated
0489     QCOMPARE(uut.isStillAlive(), true);
0490     QCOMPARE(uut.isNewPasswordRequested(), false);
0491     QCOMPARE(uut.isExistingPasswordRequested(), true);
0492     QCOMPARE(uut.isPasswordAvailable(), false);
0493     QCOMPARE(uut.isKeyAvailable(), true);
0494     QVERIFY2(uut.key(), "should have a master key by now");
0495     const auto finalChallenge = uut.challenge();
0496     QVERIFY2(finalChallenge, "should still have the same supplied password challenge after key derivation");
0497     QCOMPARE(finalChallenge->cryptText(), m_challenge->cryptText());
0498     QCOMPARE(finalChallenge->nonce(), m_challenge->nonce());
0499 
0500     QCOMPARE(newPasswordNeeded.count(), 0);
0501     QCOMPARE(passwordAvailable.count(), 2);
0502     QCOMPARE(existingPasswordNeeded.count(), 1);
0503     QCOMPARE(keyAvailable.count(), 1);
0504     QCOMPARE(requestsCancelled.count(), 0);
0505 }
0506 
0507 QTEST_MAIN(PasswordFlowTest)
0508 
0509 #include "account-secret-password-flow.moc"