END_TEST START_TEST(test_save_friend) { Tox *tox1 = tox_new(0); Tox *tox2 = tox_new(0); ck_assert_msg(tox1 || tox2, "Failed to create 2 tox instances"); uint32_t to_compare = 974536; tox_callback_friend_request(tox2, accept_friend_request, &to_compare); uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; tox_get_address(tox2, address); int test = tox_add_friend(tox1, address, (uint8_t *)"Gentoo", 7); ck_assert_msg(test == 0, "Failed to add friend error code: %i", test); uint32_t size = tox_encrypted_size(tox1); uint8_t data[size]; test = tox_encrypted_save(tox1, data, "correcthorsebatterystaple", 25); ck_assert_msg(test == 0, "failed to encrypted save"); ck_assert_msg(tox_is_data_encrypted(data) == 1, "magic number missing"); Tox *tox3 = tox_new(0); test = tox_encrypted_load(tox3, data, size, "correcthorsebatterystaple", 25); ck_assert_msg(test == 0, "failed to encrypted load"); uint8_t address2[TOX_CLIENT_ID_SIZE]; test = tox_get_client_id(tox3, 0, address2); ck_assert_msg(test == 0, "no friends!"); ck_assert_msg(memcmp(address, address2, TOX_CLIENT_ID_SIZE) == 0, "addresses don't match!"); }
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; }
/** @brief Checks if the file is serialized settings. @param filePath Path to file to check. @return False on error, true otherwise. */ bool SettingsSerializer::isSerializedFormat(QString filePath) { QFile f(filePath); if (!f.open(QIODevice::ReadOnly)) return false; char fmagic[8]; if (f.read(fmagic, sizeof(fmagic)) != sizeof(fmagic)) return false; return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted(reinterpret_cast<uint8_t*>(fmagic)); }
bool Profile::isEncrypted(QString name) { uint8_t data[encryptHeaderSize] = {0}; QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; QFile saveFile(path); if (!saveFile.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open tox save "<<path; return false; } saveFile.read((char*)data, encryptHeaderSize); saveFile.close(); return tox_is_data_encrypted(data); }
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; }
bool CToxProto::LoadToxProfile(Tox_Options *options) { debugLogA(__FUNCTION__": loading tox profile"); mir_cslock locker(profileLock); ptrT profilePath(GetToxProfilePath()); if (!IsFileExists(profilePath)) return false; FILE *profile = _tfopen(profilePath, _T("rb")); if (profile == NULL) { ShowNotification(TranslateT("Unable to open Tox profile"), MB_ICONERROR); debugLogA(__FUNCTION__": failed to open tox profile"); return false; } fseek(profile, 0, SEEK_END); long size = ftell(profile); rewind(profile); if (size < 0) { fclose(profile); return false; } if (size == 0) { fclose(profile); return true; } uint8_t *data = (uint8_t*)mir_calloc(size); if (fread((char*)data, sizeof(char), size, profile) != (size_t)size) { fclose(profile); ShowNotification(TranslateT("Unable to read Tox profile"), MB_ICONERROR); debugLogA(__FUNCTION__": failed to read tox profile"); mir_free(data); return false; } fclose(profile); if (tox_is_data_encrypted(data)) { pass_ptrA password(mir_utf8encodeW(pass_ptrT(getTStringA("Password")))); if (password == NULL || mir_strlen(password) == 0) { CToxPasswordEditor passwordEditor(this); if (!passwordEditor.DoModal()) { mir_free(data); return false; } } uint8_t *encryptedData = (uint8_t*)mir_calloc(size - TOX_PASS_ENCRYPTION_EXTRA_LENGTH); TOX_ERR_DECRYPTION coreDecryptError; if (!tox_pass_decrypt(data, size, (uint8_t*)(char*)password, mir_strlen(password), encryptedData, &coreDecryptError)) { ShowNotification(TranslateT("Unable to decrypt Tox profile"), MB_ICONERROR); debugLogA(__FUNCTION__": failed to decrypt tox profile (%d)", coreDecryptError); mir_free(data); return false; } mir_free(data); data = encryptedData; size -= TOX_PASS_ENCRYPTION_EXTRA_LENGTH; } if (data) { options->savedata_data = data; options->savedata_length = size; options->savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; return true; } return false; }
/* Returns a new Tox object on success. * If object fails to initialize the toxic process will terminate. */ static Tox *load_tox(char *data_path, struct Tox_Options *tox_opts, TOX_ERR_NEW *new_err) { Tox *m = NULL; FILE *fp = fopen(data_path, "rb"); if (fp != NULL) { /* Data file exists */ off_t len = file_size(data_path); if (len == 0) { fclose(fp); exit_toxic_err("failed in load_tox", FATALERR_FILEOP); } char data[len]; if (fread(data, sizeof(data), 1, fp) != 1) { fclose(fp); exit_toxic_err("failed in load_tox", FATALERR_FILEOP); } bool is_encrypted = tox_is_data_encrypted((uint8_t *) data); /* attempt to encrypt an already encrypted data file */ if (arg_opts.encrypt_data && is_encrypted) { fclose(fp); exit_toxic_err("failed in load_tox", FATALERR_ENCRYPT); } if (arg_opts.unencrypt_data && is_encrypted) queue_init_message("Data file '%s' has been unencrypted", data_path); else if (arg_opts.unencrypt_data) queue_init_message("Warning: passed --unencrypt-data option with unencrypted data file '%s'", data_path); if (is_encrypted) { if (!arg_opts.unencrypt_data) user_password.data_is_encrypted = true; size_t pwlen = 0; int pweval = user_settings->password_eval[0]; if (!pweval) { system("clear"); // TODO: is this portable? printf("Enter password (q to quit) "); } size_t plain_len = len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; char plain[plain_len]; while (true) { if (pweval) { pwlen = password_eval(user_password.pass, sizeof(user_password.pass)); } else { pwlen = password_prompt(user_password.pass, sizeof(user_password.pass)); } user_password.len = pwlen; if (strcasecmp(user_password.pass, "q") == 0) { fclose(fp); exit(0); } if (pwlen < MIN_PASSWORD_LEN) { system("clear"); sleep(1); printf("Invalid password. Try again. "); pweval = 0; continue; } TOX_ERR_DECRYPTION pwerr; tox_pass_decrypt((uint8_t *) data, len, (uint8_t *) user_password.pass, pwlen, (uint8_t *) plain, &pwerr); if (pwerr == TOX_ERR_DECRYPTION_OK) { tox_opts->savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; tox_opts->savedata_data = (uint8_t *) plain; tox_opts->savedata_length = plain_len; m = tox_new(tox_opts, new_err); if (m == NULL) { fclose(fp); return NULL; } break; } else if (pwerr == TOX_ERR_DECRYPTION_FAILED) { system("clear"); sleep(1); printf("Invalid password. Try again. "); pweval = 0; } else { fclose(fp); exit_toxic_err("tox_pass_decrypt() failed", pwerr); } } } else { /* data is not encrypted */ tox_opts->savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; tox_opts->savedata_data = (uint8_t *) data; tox_opts->savedata_length = len; m = tox_new(tox_opts, new_err); if (m == NULL) { fclose(fp); return NULL; } } fclose(fp); } else { /* Data file does not/should not exist */ if (file_exists(data_path)) exit_toxic_err("failed in load_tox", FATALERR_FILEOP); tox_opts->savedata_type = TOX_SAVEDATA_TYPE_NONE; m = tox_new(tox_opts, new_err); if (m == NULL) return NULL; if (store_data(m, data_path) == -1) exit_toxic_err("failed in load_tox", FATALERR_FILEOP); } return m; }
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); }
END_TEST START_TEST(test_save_friend) { Tox *tox1 = tox_new(0, 0); Tox *tox2 = tox_new(0, 0); ck_assert_msg(tox1 || tox2, "Failed to create 2 tox instances"); uint32_t to_compare = 974536; tox_callback_friend_request(tox2, accept_friend_request, &to_compare); uint8_t address[TOX_ADDRESS_SIZE]; tox_self_get_address(tox2, address); uint32_t test = tox_friend_add(tox1, address, (uint8_t *)"Gentoo", 7, 0); ck_assert_msg(test != UINT32_MAX, "Failed to add friend"); size_t size = tox_get_savedata_size(tox1); uint8_t data[size]; tox_get_savedata(tox1, data); size_t size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t enc_data[size2]; TOX_ERR_ENCRYPTION error1; bool ret = tox_pass_encrypt(data, size, "correcthorsebatterystaple", 25, enc_data, &error1); ck_assert_msg(ret, "failed to encrypted save: %u", error1); ck_assert_msg(tox_is_data_encrypted(enc_data), "magic number missing"); struct Tox_Options options; tox_options_default(&options); options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; options.savedata_data = enc_data; options.savedata_length = size2; TOX_ERR_NEW err2; Tox *tox3 = tox_new(&options, &err2); ck_assert_msg(err2 == TOX_ERR_NEW_LOAD_ENCRYPTED, "wrong error! %u. should fail with %u", err2, TOX_ERR_NEW_LOAD_ENCRYPTED); uint8_t dec_data[size]; TOX_ERR_DECRYPTION err3; ret = tox_pass_decrypt(enc_data, size2, "correcthorsebatterystaple", 25, dec_data, &err3); ck_assert_msg(ret, "failed to decrypt save: %u", err3); options.savedata_data = dec_data; options.savedata_length = size; tox3 = tox_new(&options, &err2); ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to load from decrypted data: %u", err2); uint8_t address2[TOX_PUBLIC_KEY_SIZE]; ret = tox_friend_get_public_key(tox3, 0, address2, 0); ck_assert_msg(ret, "no friends!"); ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match!"); size = tox_get_savedata_size(tox3); uint8_t data2[size]; tox_get_savedata(tox3, data2); TOX_PASS_KEY key; memcpy(key.salt, salt, 32); memcpy(key.key, known_key2, crypto_box_BEFORENMBYTES); size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t encdata2[size2]; ret = tox_pass_key_encrypt(data2, size, &key, encdata2, &error1); ck_assert_msg(ret, "failed to key encrypt %u", error1); ck_assert_msg(tox_is_data_encrypted(encdata2), "magic number the second missing"); uint8_t out1[size], out2[size]; ret = tox_pass_decrypt(encdata2, size2, pw, pwlen, out1, &err3); ck_assert_msg(ret, "failed to pw decrypt %u", err3); ret = tox_pass_key_decrypt(encdata2, size2, &key, out2, &err3); ck_assert_msg(ret, "failed to key decrypt %u", err3); ck_assert_msg(memcmp(out1, out2, size) == 0, "differing output data"); // and now with the code in use (I only bothered with manually to debug this, and it seems a waste // to remove the manual check now that it's there) options.savedata_data = out1; options.savedata_length = size; Tox *tox4 = tox_new(&options, &err2); ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to new the third"); uint8_t address5[TOX_PUBLIC_KEY_SIZE]; ret = tox_friend_get_public_key(tox4, 0, address5, 0); ck_assert_msg(ret, "no friends! the third"); ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match! the third"); tox_kill(tox1); tox_kill(tox2); tox_kill(tox3); tox_kill(tox4); }
QByteArray Core::loadToxSave(QString path) { QByteArray data; loadPath = ""; // if not empty upon return, then user forgot a password and is switching // If we can't get a lock, then another instance is already using that profile while (!ProfileLocker::lock(QFileInfo(path).baseName())) { qWarning() << "Profile "<<QFileInfo(path).baseName()<<" is already in use, pick another"; GUI::showWarning(tr("Profile already in use"), tr("Your profile is already used by another qTox\n" "Please select another profile")); QString tmppath = Settings::getInstance().askProfiles(); if (tmppath.isEmpty()) continue; Settings::getInstance().switchProfile(tmppath); path = QDir(Settings::getSettingsDirPath()).filePath(tmppath + TOX_EXT); HistoryKeeper::resetInstance(); } QFile configurationFile(path); qDebug() << "Core::loadConfiguration: reading from " << path; if (!configurationFile.exists()) { qWarning() << "The Tox configuration file "<<path<<" was not found"; return data; } if (!configurationFile.open(QIODevice::ReadOnly)) { qCritical() << "File " << path << " cannot be opened"; return data; } qint64 fileSize = configurationFile.size(); if (fileSize > 0) { data = configurationFile.readAll(); if (tox_is_data_encrypted((uint8_t*)data.data())) { if (!loadEncryptedSave(data)) { configurationFile.close(); QString profile; do { profile = Settings::getInstance().askProfiles(); } while (profile.isEmpty()); if (!profile.isEmpty()) { Settings::getInstance().switchProfile(profile); HistoryKeeper::resetInstance(); return loadToxSave(QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT)); } return QByteArray(); } } } configurationFile.close(); return data; }