encrypt filenames, keep filename when editing
This commit is contained in:
@@ -4,8 +4,8 @@ Desktop app for securely storing sensitive files
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
* **Pretty usable UI**
|
* **Pretty usable UI**
|
||||||
* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id(m=1GB,t=8,p=4) key derivation
|
* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id(m=1GB,t=6,p=4) key derivation
|
||||||
* **Cross-platform-ish:** Supports Linux and Windows
|
* **Cross-platform-ish:** Tested on Linux and Windows
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryDir>
|
||||||
#include <botan/auto_rng.h>
|
#include <botan/auto_rng.h>
|
||||||
#include <botan/hex.h>
|
#include <botan/hex.h>
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ void MainWindow::reload_fs_tree() {
|
|||||||
for (const auto &header : headers) {
|
for (const auto &header : headers) {
|
||||||
auto *item = new QTreeWidgetItem(ui->fsTreeWidget);
|
auto *item = new QTreeWidgetItem(ui->fsTreeWidget);
|
||||||
item->setText(0, QString::fromStdString(header.name));
|
item->setText(0, QString::fromStdString(header.name));
|
||||||
item->setText(1, QString::number(header.size));
|
item->setText(1, QString::number(header.content_size));
|
||||||
item->setText(2, QString::number(header.offset));
|
item->setText(2, QString::number(header.global_offset));
|
||||||
item->setText(3, QString::fromStdString(Botan::hex_encode(header.nonce)));
|
item->setText(3, QString::fromStdString(Botan::hex_encode(header.nonce)));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) {
|
for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) {
|
||||||
@@ -120,25 +120,29 @@ void MainWindow::preview_file(const std::string &filename) {
|
|||||||
void MainWindow::edit_file(const std::string &filename) {
|
void MainWindow::edit_file(const std::string &filename) {
|
||||||
auto content = m_vault->read_file(filename);
|
auto content = m_vault->read_file(filename);
|
||||||
if (content) {
|
if (content) {
|
||||||
QTemporaryFile temp_file;
|
QTemporaryDir dir;
|
||||||
ASSERT(temp_file.open());
|
ASSERT(dir.isValid());
|
||||||
{
|
|
||||||
QTextStream out(&temp_file);
|
|
||||||
out << QString::fromStdString(content.value());
|
|
||||||
}
|
|
||||||
temp_file.flush();
|
|
||||||
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(temp_file.fileName()));
|
std::string path = dir.path().toStdString() + "/" + filename;
|
||||||
|
|
||||||
|
std::ofstream file(path);
|
||||||
|
file.write(content->data(), static_cast<i64>(content->size()));
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
QDesktopServices::openUrl(
|
||||||
|
QUrl::fromLocalFile(QString::fromStdString(path)));
|
||||||
QMessageBox::information(this, "Edit",
|
QMessageBox::information(this, "Edit",
|
||||||
"Please edit the file in the opened editor and "
|
"Please edit the file in the opened editor and "
|
||||||
"save it. Click OK when done.");
|
"save it. Click OK when done.");
|
||||||
|
|
||||||
temp_file.seek(0);
|
std::ifstream file2(path, std::ios::binary);
|
||||||
QTextStream in(&temp_file);
|
std::string new_content((std::istreambuf_iterator<char>(file2)),
|
||||||
m_vault->update_file(filename, in.readAll().toStdString());
|
std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
m_vault->update_file(filename, new_content);
|
||||||
reload_fs_tree();
|
reload_fs_tree();
|
||||||
|
|
||||||
// QTemporaryFile gets deleted when it goes out of scope
|
// QTemporaryDir gets deleted when it goes out of scope
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "File to edit not found";
|
qWarning() << "File to edit not found";
|
||||||
}
|
}
|
||||||
|
|||||||
79
src/vault.cc
79
src/vault.cc
@@ -32,10 +32,10 @@ std::vector<FileHeader> Vault::read_file_headers() {
|
|||||||
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto header = read_file_header(m_file);
|
auto header = read_file_header();
|
||||||
if (header) {
|
if (header) {
|
||||||
headers.push_back(header.value());
|
headers.push_back(header.value());
|
||||||
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
|
m_file.seekg(static_cast<i64>(header->content_size), std::ios::cur);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -49,16 +49,16 @@ std::optional<std::string> Vault::read_file(const std::string &filename) {
|
|||||||
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto header = read_file_header(m_file);
|
auto header = read_file_header();
|
||||||
if (!header) {
|
if (!header) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header->name == filename) {
|
if (header->name == filename) {
|
||||||
Botan::secure_vector<u8> ciphertext;
|
Botan::secure_vector<u8> ciphertext;
|
||||||
ciphertext.resize(header->size);
|
ciphertext.resize(header->content_size);
|
||||||
if (!m_file.read(to_char_ptr(ciphertext.data()),
|
if (!m_file.read(to_char_ptr(ciphertext.data()),
|
||||||
static_cast<i64>(header->size))) {
|
static_cast<i64>(header->content_size))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ std::optional<std::string> Vault::read_file(const std::string &filename) {
|
|||||||
return std::string(to_char_ptr(plaintext.data()), plaintext.size());
|
return std::string(to_char_ptr(plaintext.data()), plaintext.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
|
m_file.seekg(static_cast<i64>(header->content_size), std::ios::cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -78,22 +78,24 @@ void Vault::create_file(const std::string &filename,
|
|||||||
m_file.clear();
|
m_file.clear();
|
||||||
m_file.seekp(0, std::ios::end);
|
m_file.seekp(0, std::ios::end);
|
||||||
|
|
||||||
u64 filename_size = filename.size();
|
|
||||||
|
|
||||||
Botan::secure_vector<u8> plaintext(content.begin(), content.end());
|
|
||||||
|
|
||||||
static Botan::AutoSeeded_RNG rng;
|
static Botan::AutoSeeded_RNG rng;
|
||||||
auto nonce_sv = rng.random_vec(24);
|
auto nonce_sv = rng.random_vec(24);
|
||||||
std::vector<u8> nonce(nonce_sv.begin(), nonce_sv.end());
|
std::vector<u8> nonce(nonce_sv.begin(), nonce_sv.end());
|
||||||
|
|
||||||
auto ciphertext = Crypto::encrypt_xchacha20_poly1305(plaintext, m_key, nonce);
|
Botan::secure_vector<u8> filename_sv(filename.begin(), filename.end());
|
||||||
|
auto filename_ciphertext =
|
||||||
|
Crypto::encrypt_xchacha20_poly1305(filename_sv, m_key, nonce);
|
||||||
|
u64 filename_ciphertext_size = filename_ciphertext.size();
|
||||||
|
|
||||||
|
Botan::secure_vector<u8> content_sv(content.begin(), content.end());
|
||||||
|
auto ciphertext =
|
||||||
|
Crypto::encrypt_xchacha20_poly1305(content_sv, m_key, nonce);
|
||||||
u64 ciphertext_size = ciphertext.size();
|
u64 ciphertext_size = ciphertext.size();
|
||||||
|
|
||||||
// TODO: encrypt filenames as well
|
|
||||||
|
|
||||||
ASSERT(m_file.write(to_char_ptr(&filename_size), sizeof(u64)));
|
|
||||||
ASSERT(m_file.write(filename.data(), static_cast<i64>(filename_size)));
|
|
||||||
ASSERT(m_file.write(to_char_ptr(nonce.data()), nonce.size()));
|
ASSERT(m_file.write(to_char_ptr(nonce.data()), nonce.size()));
|
||||||
|
ASSERT(m_file.write(to_char_ptr(&filename_ciphertext_size), sizeof(u64)));
|
||||||
|
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()),
|
||||||
|
static_cast<i64>(filename_ciphertext_size)));
|
||||||
ASSERT(m_file.write(to_char_ptr(&ciphertext_size), sizeof(u64)));
|
ASSERT(m_file.write(to_char_ptr(&ciphertext_size), sizeof(u64)));
|
||||||
ASSERT(m_file.write(to_char_ptr(ciphertext.data()),
|
ASSERT(m_file.write(to_char_ptr(ciphertext.data()),
|
||||||
static_cast<i64>(ciphertext_size)));
|
static_cast<i64>(ciphertext_size)));
|
||||||
@@ -110,20 +112,20 @@ void Vault::delete_file(const std::string &filename) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
i64 current_pos = m_file.tellg();
|
i64 current_pos = m_file.tellg();
|
||||||
|
|
||||||
auto header = read_file_header(m_file);
|
auto header = read_file_header();
|
||||||
if (!header) {
|
if (!header) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header->name == filename) {
|
if (header->name == filename) {
|
||||||
entry_start = current_pos;
|
entry_start = current_pos;
|
||||||
entry_total_size =
|
entry_total_size = 24 + sizeof(u64) + header->name_ciphertext_size +
|
||||||
sizeof(u64) + header->name.length() + 24 + sizeof(u64) + header->size;
|
sizeof(u64) + header->content_size;
|
||||||
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
|
m_file.seekg(static_cast<i64>(header->content_size), std::ios::cur);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
|
m_file.seekg(static_cast<i64>(header->content_size), std::ios::cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry_start != -1) {
|
if (entry_start != -1) {
|
||||||
@@ -156,28 +158,33 @@ void Vault::update_file(const std::string &filename,
|
|||||||
create_file(filename, content);
|
create_file(filename, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FileHeader> Vault::read_file_header(std::fstream &file) {
|
std::optional<FileHeader> Vault::read_file_header() {
|
||||||
FileHeader header{};
|
FileHeader header{};
|
||||||
header.offset = file.tellg();
|
header.global_offset = m_file.tellg();
|
||||||
|
|
||||||
u64 name_size = 0;
|
|
||||||
if (!file.read(to_char_ptr(&name_size), sizeof(u64))) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(name_size < 10000);
|
|
||||||
|
|
||||||
header.name.resize(name_size);
|
|
||||||
if (!file.read(header.name.data(), static_cast<i64>(name_size))) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.nonce.resize(24);
|
header.nonce.resize(24);
|
||||||
if (!file.read(to_char_ptr(header.nonce.data()), 24)) {
|
if (!m_file.read(to_char_ptr(header.nonce.data()), 24)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.read(to_char_ptr(&header.size), sizeof(u64))) {
|
if (!m_file.read(to_char_ptr(&header.name_ciphertext_size), sizeof(u64))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(header.name_ciphertext_size < 10000);
|
||||||
|
|
||||||
|
Botan::secure_vector<u8> name_ciphertext;
|
||||||
|
name_ciphertext.resize(header.name_ciphertext_size);
|
||||||
|
if (!m_file.read(to_char_ptr(name_ciphertext.data()),
|
||||||
|
static_cast<i64>(header.name_ciphertext_size))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto name =
|
||||||
|
Crypto::decrypt_xchacha20_poly1305(name_ciphertext, m_key, header.nonce);
|
||||||
|
header.name = std::string(name.begin(), name.end());
|
||||||
|
|
||||||
|
if (!m_file.read(to_char_ptr(&header.content_size), sizeof(u64))) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return header;
|
return header;
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ constexpr u64 AFTER_HEADER_OFFSET = 22;
|
|||||||
|
|
||||||
struct FileHeader {
|
struct FileHeader {
|
||||||
std::string name;
|
std::string name;
|
||||||
u64 size;
|
u64 name_ciphertext_size;
|
||||||
|
u64 content_size;
|
||||||
std::vector<u8> nonce;
|
std::vector<u8> nonce;
|
||||||
u64 offset;
|
u64 global_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Vault {
|
class Vault {
|
||||||
@@ -26,10 +27,10 @@ public:
|
|||||||
void delete_file(const std::string &name);
|
void delete_file(const std::string &name);
|
||||||
void update_file(const std::string &name, const std::string &content);
|
void update_file(const std::string &name, const std::string &content);
|
||||||
|
|
||||||
static std::optional<FileHeader> read_file_header(std::fstream &file);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_path;
|
std::string m_path;
|
||||||
std::fstream m_file;
|
std::fstream m_file;
|
||||||
Botan::secure_vector<u8> m_key;
|
Botan::secure_vector<u8> m_key;
|
||||||
|
|
||||||
|
std::optional<FileHeader> read_file_header();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user