/** * @brief Get a contact's avatar from cache, with a specified profile password. * @param ownerId Friend ID to load avatar. * @param password Profile password to decrypt data. * @return Avatar as QByteArray. */ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& password) { QString path = avatarPath(ownerId); bool encrypted = !password.isEmpty(); // If the encrypted avatar isn't found, try loading the unencrypted one for the same ID if (!password.isEmpty() && !QFile::exists(path)) { encrypted = false; path = avatarPath(ownerId, true); } QFile file(path); if (!file.open(QIODevice::ReadOnly)) { return {}; } QByteArray pic = file.readAll(); if (encrypted && !pic.isEmpty()) { uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast<uint8_t*>(pic.data()), salt); auto passkey = core->createPasskey(password, salt); pic = core->decryptData(pic, *passkey); } return pic; }
QByteArray Profile::loadToxSave() { assert(!isRemoved); /// TODO: Cache the data, invalidate it only when we save QByteArray data; QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; QFile saveFile(path); qint64 fileSize; qDebug() << "Loading tox save "<<path; if (!saveFile.exists()) { qWarning() << "The tox save file "<<path<<" was not found"; goto fail; } if (!saveFile.open(QIODevice::ReadOnly)) { qCritical() << "The tox save file " << path << " couldn't' be opened"; goto fail; } fileSize = saveFile.size(); if (fileSize <= 0) { qWarning() << "The tox save file"<<path<<" is empty!"; goto fail; } data = saveFile.readAll(); if (tox_is_data_encrypted((uint8_t*)data.data())) { if (password.isEmpty()) { qCritical() << "The tox save file is encrypted, but we don't have a password!"; data.clear(); goto fail; } uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); passkey = *core->createPasskey(password, salt); data = core->decryptData(data, passkey); if (data.isEmpty()) qCritical() << "Failed to decrypt the tox save file"; } else { if (!password.isEmpty()) qWarning() << "We have a password, but the tox save file is not encrypted"; } fail: saveFile.close(); return data; }
bool Core::loadEncryptedSave(QByteArray& data) { if (!Settings::getInstance().getEncryptTox()) GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); size_t fileSize = data.size(); int error = -1; QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile())); QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); QString dialogtxt; if (pwsaltedkeys[ptMain]) // password set, try it { QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); if (tox_pass_key_decrypt((uint8_t*)data.data(), fileSize, pwsaltedkeys[ptMain], (uint8_t*)newData.data(), nullptr)) { data = newData; Settings::getInstance().setEncryptTox(true); return true; } dialogtxt = tr("The profile password failed. Please try another?", "used only when pw set before load() doesn't work"); } else { dialogtxt = a; } uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); do { QString pw = GUI::passwordDialog(tr("Change profile"), dialogtxt); if (pw.isEmpty()) { clearPassword(ptMain); return false; } else { setPassword(pw, ptMain, salt); } QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); error = !tox_pass_key_decrypt((uint8_t*)data.data(), data.size(), pwsaltedkeys[ptMain], (uint8_t*)newData.data(), nullptr); if (!error) data = newData; dialogtxt = a + "\n" + b; } while (error != 0); Settings::getInstance().setEncryptTox(true); return true; }
END_TEST START_TEST(test_keys) { TOX_ERR_ENCRYPTION encerr; TOX_ERR_DECRYPTION decerr; TOX_ERR_KEY_DERIVATION keyerr; TOX_PASS_KEY key; bool ret = tox_derive_key_from_pass("123qweasdzxc", 12, &key, &keyerr); ck_assert_msg(ret, "generic failure 1: %u", keyerr); uint8_t *string = "No Patrick, mayonnaise is not an instrument."; // 44 uint8_t encrypted[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; ret = tox_pass_key_encrypt(string, 44, &key, encrypted, &encerr); ck_assert_msg(ret, "generic failure 2: %u", encerr); uint8_t encrypted2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; ret = tox_pass_encrypt(string, 44, "123qweasdzxc", 12, encrypted2, &encerr); ck_assert_msg(ret, "generic failure 3: %u", encerr); uint8_t out1[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; uint8_t out2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; ret = tox_pass_key_decrypt(encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, &key, out1, &decerr); ck_assert_msg(ret, "generic failure 4: %u", decerr); ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 1 failed"); ret = tox_pass_decrypt(encrypted2, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, "123qweasdzxc", 12, out2, &decerr); ck_assert_msg(ret, "generic failure 5: %u", decerr); ck_assert_msg(memcmp(out2, string, 44) == 0, "decryption 2 failed"); // test that pass_decrypt can decrypt things from pass_key_encrypt ret = tox_pass_decrypt(encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, "123qweasdzxc", 12, out1, &decerr); ck_assert_msg(ret, "generic failure 6: %u", decerr); ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 3 failed"); uint8_t salt[TOX_PASS_SALT_LENGTH]; ck_assert_msg(tox_get_salt(encrypted, salt), "couldn't get salt"); TOX_PASS_KEY key2; ret = tox_derive_key_with_salt("123qweasdzxc", 12, salt, &key2, &keyerr); ck_assert_msg(ret, "generic failure 7: %u", keyerr); ck_assert_msg(0 == memcmp(&key, &key2, sizeof(TOX_PASS_KEY)), "salt comparison failed"); }
QByteArray Core::getSaltFromFile(QString filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "file" << filename << "doesn't exist"; return QByteArray(); } QByteArray data = file.read(TOX_PASS_ENCRYPTION_EXTRA_LENGTH); file.close(); uint8_t salt[TOX_PASS_SALT_LENGTH]; if (!tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt, nullptr)) { qWarning() << "can't get salt from" << filename << "header"; return QByteArray(); } QByteArray res(reinterpret_cast<const char*>(salt), TOX_PASS_SALT_LENGTH); return res; }
void SettingsSerializer::readSerialized() { QFile f(path); if (!f.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file"; return; } QByteArray data = f.readAll(); f.close(); // Decrypt if (tox_is_data_encrypted(reinterpret_cast<uint8_t*>(data.data()))) { if (password.isEmpty()) { qCritical() << "The settings file is encrypted, but we don't have a password!"; return; } Core* core = Nexus::getCore(); uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); auto passkey = core->createPasskey(password, salt); data = core->decryptData(data, *passkey); if (data.isEmpty()) { qCritical() << "Failed to decrypt the settings file"; return; } } else { if (!password.isEmpty()) qWarning() << "We have a password, but the settings file is not encrypted"; } if (memcmp(data.data(), magic, 4)) { qWarning() << "Bad magic!"; return; } data = data.mid(4); QDataStream stream(&data, QIODevice::ReadOnly); stream.setVersion(QDataStream::Qt_5_0); while (!stream.atEnd()) { RecordTag tag; readStream(stream, tag); if (tag == RecordTag::Value) { QByteArray key; QByteArray value; readStream(stream, key); readStream(stream, value); setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); } else if (tag == RecordTag::GroupStart) { QByteArray prefix; readStream(stream, prefix); beginGroup(QString::fromUtf8(prefix)); } else if (tag == RecordTag::ArrayStart) { QByteArray prefix; readStream(stream, prefix); beginReadArray(QString::fromUtf8(prefix)); QByteArray sizeData; readStream(stream, sizeData); if (sizeData.isEmpty()) { qWarning("The personal save file is corrupted!"); return; } int size = dataToVInt(sizeData); arrays[array].size = qMax(size, arrays[array].size); } else if (tag == RecordTag::ArrayValue) { QByteArray indexData; readStream(stream, indexData); if (indexData.isEmpty()) { qWarning("The personal save file is corrupted!"); return; } setArrayIndex(dataToVInt(indexData)); QByteArray key; QByteArray value; readStream(stream, key); readStream(stream, value); setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); } else if (tag == RecordTag::ArrayEnd) { endArray(); } } group = array = -1; }
Profile* Profile::loadProfile(QString name, QString password) { if (ProfileLocker::hasLock()) { qCritical() << "Tried to load profile "<<name<<", but another profile is already locked!"; return nullptr; } if (!ProfileLocker::lock(name)) { qWarning() << "Failed to lock profile "<<name; return nullptr; } // Check password { QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; QFile saveFile(path); qDebug() << "Loading tox save "<<path; if (!saveFile.exists()) { qWarning() << "The tox save file "<<path<<" was not found"; ProfileLocker::unlock(); return nullptr; } if (!saveFile.open(QIODevice::ReadOnly)) { qCritical() << "The tox save file " << path << " couldn't' be opened"; ProfileLocker::unlock(); return nullptr; } qint64 fileSize = saveFile.size(); if (fileSize <= 0) { qWarning() << "The tox save file"<<path<<" is empty!"; ProfileLocker::unlock(); return nullptr; } QByteArray data = saveFile.readAll(); if (tox_is_data_encrypted((uint8_t*)data.data())) { if (password.isEmpty()) { qCritical() << "The tox save file is encrypted, but we don't have a password!"; ProfileLocker::unlock(); return nullptr; } uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); auto tmpkey = *Core::createPasskey(password, salt); data = Core::decryptData(data, tmpkey); if (data.isEmpty()) { qCritical() << "Failed to decrypt the tox save file"; ProfileLocker::unlock(); return nullptr; } } else { if (!password.isEmpty()) qWarning() << "We have a password, but the tox save file is not encrypted"; } } return new Profile(name, password, false); }