open file without editing action

This commit is contained in:
2026-04-25 15:05:19 +02:00
parent 48fc35e16c
commit 99d239ea08
4 changed files with 102 additions and 39 deletions

View File

@@ -6,23 +6,22 @@
namespace Crypto { namespace Crypto {
inline Botan::secure_vector<u8> inline Botan::secure_vector<u8> encrypt(const Botan::secure_vector<u8> &key,
encrypt(const Botan::secure_vector<u8> &key, const std::span<const u8> &plaintext) {
const Botan::secure_vector<u8> &plaintext) {
ASSERT(key.size() == 32); ASSERT(key.size() == 32);
static Botan::AutoSeeded_RNG rng; static Botan::AutoSeeded_RNG rng;
auto nonce = rng.random_array<24>();
// XChaCha20 is selected automatically based on the nonce size // XChaCha20 is selected automatically based on the nonce size
// https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375 // https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375
auto cipher = Botan::AEAD_Mode::create_or_throw( static auto cipher = Botan::AEAD_Mode::create_or_throw(
"ChaCha20Poly1305", Botan::Cipher_Dir::Encryption); "ChaCha20Poly1305", Botan::Cipher_Dir::Encryption);
auto nonce = rng.random_array<24>();
cipher->set_key(key); cipher->set_key(key);
cipher->start(nonce); cipher->start(nonce);
Botan::secure_vector<u8> ciphertext = plaintext; Botan::secure_vector<u8> ciphertext(plaintext.begin(), plaintext.end());
cipher->finish(ciphertext); cipher->finish(ciphertext);
Botan::secure_vector<u8> res; Botan::secure_vector<u8> res;

View File

@@ -7,6 +7,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QMimeData> #include <QMimeData>
#include <QTemporaryDir> #include <QTemporaryDir>
#include <array>
#include <botan/exceptn.h> #include <botan/exceptn.h>
namespace { namespace {
@@ -16,6 +17,21 @@ std::string basename(const std::string &path) {
return (pos == std::string::npos) ? path : path.substr(pos + 1); return (pos == std::string::npos) ? path : path.substr(pos + 1);
} }
std::string format_bytes(u64 bytes) {
const std::array<std::string_view, 5> units = {"B", "KB", "MB", "GB", "TB"};
auto size = static_cast<f64>(bytes);
i32 unit = 0;
while (size >= 1024.0 && unit < 5) {
size /= 1024.0;
++unit;
}
std::ostringstream out;
out << std::fixed << std::setprecision(2) << size << " " << units.at(unit);
return out.str();
}
} // namespace } // namespace
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
@@ -106,11 +122,15 @@ MainWindow::MainWindow(QWidget *parent)
QStringList paths = QStringList paths =
QFileDialog::getOpenFileNames(this, "Choose files to add"); QFileDialog::getOpenFileNames(this, "Choose files to add");
for (const auto &path : paths) { for (const auto &path : paths) {
std::ifstream file(path.toStdString(), std::ios::binary); ui->statusbar->showMessage("Adding " + path + "...");
QCoreApplication::processEvents();
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
std::ifstream file(path.toStdString(), std::ios::binary | std::ios::ate);
ASSERT(file.good());
auto size = file.tellg();
file.seekg(0);
std::string content(size, '\0');
file.read(content.data(), size);
m_vault->create_file(basename(path.toStdString()), content); m_vault->create_file(basename(path.toStdString()), content);
} }
@@ -134,6 +154,9 @@ MainWindow::MainWindow(QWidget *parent)
for (const auto &header : headers) { for (const auto &header : headers) {
auto content = m_vault->read_file(header.name); auto content = m_vault->read_file(header.name);
if (content) { if (content) {
ui->statusbar->showMessage("Extracting " +
QString::fromStdString(header.name) + "...");
QCoreApplication::processEvents();
std::ofstream file(path.toStdString() + "/" + header.name, std::ofstream file(path.toStdString() + "/" + header.name,
std::ios::binary); std::ios::binary);
file.write(content->data(), static_cast<i64>(content->size())); file.write(content->data(), static_cast<i64>(content->size()));
@@ -153,7 +176,8 @@ void MainWindow::reload_fs_tree() {
auto *item = new QTreeWidgetItem(ui->fsTreeWidget); auto *item = new QTreeWidgetItem(ui->fsTreeWidget);
item->setIcon(0, style()->standardIcon(QStyle::SP_FileIcon)); item->setIcon(0, style()->standardIcon(QStyle::SP_FileIcon));
item->setText(0, QString::fromStdString(header.name)); item->setText(0, QString::fromStdString(header.name));
item->setText(1, QString::number(header.content_ciphertext_size)); item->setText(1, QString::fromStdString(
format_bytes(header.content_ciphertext_size)));
} }
for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) { for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) {
ui->fsTreeWidget->resizeColumnToContents(i); ui->fsTreeWidget->resizeColumnToContents(i);
@@ -190,6 +214,29 @@ void MainWindow::extract_file(const std::string &filename) {
} }
} }
void MainWindow::open_file(const std::string &filename) {
auto content = m_vault->read_file(filename);
if (content) {
QTemporaryDir dir;
ASSERT(dir.isValid());
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, "Open",
"Click OK to delete the opened file.");
// QTemporaryDir gets deleted when it goes out of scope
} else {
qWarning() << "File to open not found";
}
}
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) {
@@ -236,6 +283,12 @@ void MainWindow::file_context_menu(const QPoint &pos) {
connect(preview_action, &QAction::triggered, this, connect(preview_action, &QAction::triggered, this,
[this, item]() { preview_file(item->text(0).toStdString()); }); [this, item]() { preview_file(item->text(0).toStdString()); });
QAction *open_action =
menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView),
"Open in external app");
connect(open_action, &QAction::triggered, this,
[this, item]() { open_file(item->text(0).toStdString()); });
QAction *edit_action = QAction *edit_action =
menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView),
"Edit in external app"); "Edit in external app");
@@ -283,12 +336,14 @@ void MainWindow::dropEvent(QDropEvent *event) {
if (!u.isLocalFile()) { if (!u.isLocalFile()) {
continue; continue;
} }
std::ifstream file(u.toLocalFile().toStdString(), std::ios::binary);
std::ifstream file(u.toLocalFile().toStdString(),
std::ios::binary | std::ios::ate);
ASSERT(file.good()); ASSERT(file.good());
auto size = file.tellg();
std::string content((std::istreambuf_iterator<char>(file)), file.seekg(0);
std::istreambuf_iterator<char>()); std::string content(size, '\0');
file.read(content.data(), size);
m_vault->create_file(basename(u.toLocalFile().toStdString()), content); m_vault->create_file(basename(u.toLocalFile().toStdString()), content);
} }
ui->statusbar->showMessage( ui->statusbar->showMessage(

View File

@@ -21,6 +21,7 @@ private:
void reload_fs_tree(); void reload_fs_tree();
void preview_file(const std::string &filename); void preview_file(const std::string &filename);
void extract_file(const std::string &filename); void extract_file(const std::string &filename);
void open_file(const std::string &filename);
void edit_file(const std::string &filename); void edit_file(const std::string &filename);
void file_context_menu(const QPoint &pos); void file_context_menu(const QPoint &pos);
}; };

View File

@@ -3,6 +3,9 @@
#include "crypto.h" #include "crypto.h"
#include <botan/auto_rng.h> #include <botan/auto_rng.h>
#define WRITE(file, data, size) \
ASSERT(file.write(to_char_ptr(data), static_cast<i64>(size)))
namespace { namespace {
template <typename T> constexpr char *to_char_ptr(T *ptr) noexcept { template <typename T> constexpr char *to_char_ptr(T *ptr) noexcept {
@@ -13,6 +16,10 @@ template <typename T> constexpr const char *to_char_ptr(const T *ptr) noexcept {
return reinterpret_cast<const char *>(ptr); return reinterpret_cast<const char *>(ptr);
} }
template <typename T> constexpr std::span<const u8> to_span(const T &x) {
return std::span<const u8>(reinterpret_cast<const u8 *>(x.data()), x.size());
}
} // namespace } // namespace
Vault::Vault(std::string path, const std::string &password) Vault::Vault(std::string path, const std::string &password)
@@ -47,16 +54,14 @@ void Vault::create(const std::string &path, const std::string &password) {
auto key = Crypto::derive_key(password, salt); auto key = Crypto::derive_key(password, salt);
const std::string_view content = "LETSGO"; const std::string_view content = "LETSGO";
Botan::secure_vector<u8> content_sv(content.begin(), content.end()); auto check_ciphertext = Crypto::encrypt(key, to_span(content));
auto check_ciphertext = Crypto::encrypt(key, content_sv);
std::ofstream create(path, std::ios::binary); std::ofstream file(path, std::ios::binary);
create.write("DULL", 4); WRITE(file, "DULL", 4);
create.write(to_char_ptr(&VERSION), sizeof(VERSION)); WRITE(file, to_char_ptr(&VERSION), sizeof(VERSION));
create.write(to_char_ptr(salt.data()), 16); WRITE(file, to_char_ptr(salt.data()), 16);
WRITE(file, to_char_ptr(check_ciphertext.data()), 46);
create.write(to_char_ptr(check_ciphertext.data()), 46); file.close();
create.close();
} }
std::vector<FileHeader> Vault::read_file_headers() { std::vector<FileHeader> Vault::read_file_headers() {
@@ -115,21 +120,18 @@ 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);
Botan::secure_vector<u8> filename_sv(filename.begin(), filename.end()); auto filename_ciphertext = Crypto::encrypt(m_key, to_span(filename));
auto filename_ciphertext = Crypto::encrypt(m_key, filename_sv);
u64 filename_ciphertext_size = filename_ciphertext.size(); u64 filename_ciphertext_size = filename_ciphertext.size();
Botan::secure_vector<u8> content_sv(content.begin(), content.end()); auto ciphertext = Crypto::encrypt(m_key, to_span(content));
auto ciphertext = Crypto::encrypt(m_key, content_sv);
u64 ciphertext_size = ciphertext.size(); u64 ciphertext_size = ciphertext.size();
ASSERT(m_file.write("\0", 1)); // deleted flag bool deleted = false;
ASSERT(m_file.write(to_char_ptr(&filename_ciphertext_size), sizeof(u64))); WRITE(m_file, &deleted, sizeof(bool)); // deleted flag
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()), WRITE(m_file, &filename_ciphertext_size, sizeof(u64));
static_cast<i64>(filename_ciphertext_size))); WRITE(m_file, filename_ciphertext.data(), filename_ciphertext_size);
ASSERT(m_file.write(to_char_ptr(&ciphertext_size), sizeof(u64))); WRITE(m_file, &ciphertext_size, sizeof(u64));
ASSERT(m_file.write(to_char_ptr(ciphertext.data()), WRITE(m_file, ciphertext.data(), ciphertext_size);
static_cast<i64>(ciphertext_size)));
m_file.flush(); m_file.flush();
} }
@@ -148,12 +150,18 @@ void Vault::delete_file(const std::string &filename) {
if (!header->deleted && header->name == filename) { if (!header->deleted && header->name == filename) {
// zero the ciphertext and mark as deleted // zero the ciphertext and mark as deleted
m_file.seekp(m_file.tellg()); m_file.seekp(m_file.tellg());
for (size_t i = 0; i < header->content_ciphertext_size; i++) { std::array<u8, 4096> zeros = {0};
m_file.write(to_char_ptr("\0"), 1); u64 remaining = header->content_ciphertext_size;
while (remaining > 0) {
u64 chunk = std::min(remaining, sizeof(zeros));
WRITE(m_file, zeros.data(), chunk);
remaining -= chunk;
} }
m_file.seekp(current_pos, std::ios::beg); m_file.seekp(current_pos, std::ios::beg);
m_file.write(to_char_ptr("\1"), 1); bool deleted = true;
WRITE(m_file, &deleted, sizeof(bool));
m_file.flush();
break; break;
} }