per-vault salt, readme, windows
This commit is contained in:
@@ -5,8 +5,10 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
||||||
find_package(PkgConfig REQUIRED)
|
if(NOT WIN32)
|
||||||
pkg_check_modules(BOTAN REQUIRED botan-3)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(BOTAN REQUIRED botan-3)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|||||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# dull
|
||||||
|
|
||||||
|
Desktop app for securely storing sensitive files
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* **Pretty usable UI**
|
||||||
|
* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id(m=1GB,t=8,p=4) key derivation
|
||||||
|
* **Cross-platform-ish:** Supports Linux and Windows
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Debian/Ubuntu
|
||||||
|
```
|
||||||
|
sudo apt update && sudo apt install build-essential qt6-base-dev libbotan-3-dev cmake
|
||||||
|
|
||||||
|
cmake -S . -B build
|
||||||
|
cmake --build build -j $(nproc)
|
||||||
|
./build/dull
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows / MSYS2
|
||||||
|
```
|
||||||
|
pacman -S --needed mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-libbotan
|
||||||
|
|
||||||
|
cmake -S . -B build -DBOTAN_INCLUDE_DIRS=/mingw64/include/botan-3 -DBOTAN_LIBRARIES=/mingw64/lib/libbotan-3.a
|
||||||
|
cmake --build build -j $(nproc)
|
||||||
|
./build/dull
|
||||||
|
```
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include <botan/aead.h>
|
#include <botan/aead.h>
|
||||||
#include <botan/auto_rng.h>
|
|
||||||
#include <botan/hex.h>
|
#include <botan/hex.h>
|
||||||
#include <botan/pwdhash.h>
|
#include <botan/pwdhash.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Crypto {
|
namespace Crypto {
|
||||||
|
|
||||||
// TODO: random salt per per-vault
|
|
||||||
const std::vector<u8> SALT = {0xab, 0xfb, 0x45, 0x1, 0x62, 0xcf, 0xd7, 0x46,
|
|
||||||
0xdf, 0xaf, 0x4e, 0xa9, 0xf0, 0x4b, 0x9f, 0x38};
|
|
||||||
|
|
||||||
inline Botan::secure_vector<u8>
|
inline Botan::secure_vector<u8>
|
||||||
encrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &plaintext,
|
encrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &plaintext,
|
||||||
const Botan::secure_vector<u8> &key,
|
const Botan::secure_vector<u8> &key,
|
||||||
@@ -55,6 +50,7 @@ decrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &ciphertext,
|
|||||||
|
|
||||||
inline Botan::secure_vector<u8>
|
inline Botan::secure_vector<u8>
|
||||||
derive_key_argon2id(const std::string &password, const std::vector<u8> &salt) {
|
derive_key_argon2id(const std::string &password, const std::vector<u8> &salt) {
|
||||||
|
// thousands of years to crack a random 8 char password on a 100 GPUs
|
||||||
auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id")
|
auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id")
|
||||||
->from_params(static_cast<u64>(1024 * 1024), 8, 4);
|
->from_params(static_cast<u64>(1024 * 1024), 8, 4);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
#include <botan/auto_rng.h>
|
||||||
#include <botan/hex.h>
|
#include <botan/hex.h>
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
@@ -22,15 +23,20 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
|
|
||||||
QString password = QInputDialog::getText(
|
QString password = QInputDialog::getText(
|
||||||
this, "Choose a password", "Choose a password", QLineEdit::Password);
|
this, "Choose a password", "Choose a password", QLineEdit::Password);
|
||||||
if (password.length() < 5) {
|
if (password.length() < 8) {
|
||||||
QMessageBox::critical(this, "Error",
|
QMessageBox::critical(this, "Error",
|
||||||
"Password must be at least 5 characters long.");
|
"Password must be at least 8 characters long.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Botan::AutoSeeded_RNG rng;
|
||||||
|
auto salt_sv = rng.random_vec(16);
|
||||||
|
std::vector<u8> salt(salt_sv.begin(), salt_sv.end());
|
||||||
|
|
||||||
std::ofstream create(path.toStdString(), std::ios::binary);
|
std::ofstream create(path.toStdString(), std::ios::binary);
|
||||||
create.write("DULL", 4);
|
create.write("DULL", 4);
|
||||||
create.write(to_char_ptr(&VERSION), sizeof(VERSION));
|
create.write(to_char_ptr(&VERSION), sizeof(VERSION));
|
||||||
|
create.write(to_char_ptr(salt.data()), 16);
|
||||||
create.close();
|
create.close();
|
||||||
|
|
||||||
m_vault =
|
m_vault =
|
||||||
@@ -48,11 +54,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
|
|
||||||
QString password = QInputDialog::getText(
|
QString password = QInputDialog::getText(
|
||||||
this, "Unlock the vault", "Enter vault password", QLineEdit::Password);
|
this, "Unlock the vault", "Enter vault password", QLineEdit::Password);
|
||||||
if (password.length() < 5) {
|
|
||||||
QMessageBox::critical(this, "Error",
|
|
||||||
"Password must be at least 5 characters long.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if password valid
|
// TODO: check if password valid
|
||||||
|
|
||||||
@@ -100,7 +101,9 @@ void MainWindow::reload_fs_tree() {
|
|||||||
item->setText(2, QString::number(header.offset));
|
item->setText(2, QString::number(header.offset));
|
||||||
item->setText(3, QString::fromStdString(Botan::hex_encode(header.nonce)));
|
item->setText(3, QString::fromStdString(Botan::hex_encode(header.nonce)));
|
||||||
}
|
}
|
||||||
ui->fsTreeWidget->resizeColumnToContents(0);
|
for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) {
|
||||||
|
ui->fsTreeWidget->resizeColumnToContents(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::preview_file(const std::string &filename) {
|
void MainWindow::preview_file(const std::string &filename) {
|
||||||
@@ -159,5 +162,12 @@ void MainWindow::file_context_menu(const QPoint &pos) {
|
|||||||
connect(edit_action, &QAction::triggered, this,
|
connect(edit_action, &QAction::triggered, this,
|
||||||
[this, item]() { edit_file(item->text(0).toStdString()); });
|
[this, item]() { edit_file(item->text(0).toStdString()); });
|
||||||
|
|
||||||
|
QAction *delete_action = menu.addAction(
|
||||||
|
style()->standardIcon(QStyle::SP_DialogCancelButton), "Delete");
|
||||||
|
connect(delete_action, &QAction::triggered, this, [this, item]() {
|
||||||
|
m_vault->delete_file(item->text(0).toStdString());
|
||||||
|
reload_fs_tree();
|
||||||
|
});
|
||||||
|
|
||||||
menu.exec(ui->fsTreeWidget->mapToGlobal(pos));
|
menu.exec(ui->fsTreeWidget->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/vault.cc
17
src/vault.cc
@@ -2,6 +2,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <botan/auto_rng.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
Vault::Vault(std::string path, const std::string &password)
|
Vault::Vault(std::string path, const std::string &password)
|
||||||
@@ -9,15 +10,19 @@ Vault::Vault(std::string path, const std::string &password)
|
|||||||
m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary);
|
m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary);
|
||||||
ASSERT(m_file.good());
|
ASSERT(m_file.good());
|
||||||
|
|
||||||
m_key = Crypto::derive_key_argon2id(password, Crypto::SALT);
|
std::array<char, 4> magic{};
|
||||||
|
ASSERT(m_file.read(magic.data(), 4));
|
||||||
std::array<char, 4> header{};
|
ASSERT(std::string_view(magic.data(), magic.size()) == "DULL");
|
||||||
ASSERT(static_cast<bool>(m_file.read(header.data(), header.size())));
|
|
||||||
ASSERT(std::string_view(header.data(), header.size()) == "DULL");
|
|
||||||
|
|
||||||
i16 version = 0;
|
i16 version = 0;
|
||||||
ASSERT(m_file.read(to_char_ptr(&version), sizeof(version)));
|
ASSERT(m_file.read(to_char_ptr(&version), sizeof(version)));
|
||||||
ASSERT(version == VERSION);
|
ASSERT(version == VERSION);
|
||||||
|
|
||||||
|
std::vector<u8> salt{};
|
||||||
|
salt.resize(16);
|
||||||
|
ASSERT(m_file.read(to_char_ptr(salt.data()), 16));
|
||||||
|
|
||||||
|
m_key = Crypto::derive_key_argon2id(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<FileHeader> Vault::read_file_headers() {
|
std::vector<FileHeader> Vault::read_file_headers() {
|
||||||
@@ -77,7 +82,7 @@ void Vault::create_file(const std::string &filename,
|
|||||||
|
|
||||||
Botan::secure_vector<u8> plaintext(content.begin(), content.end());
|
Botan::secure_vector<u8> plaintext(content.begin(), content.end());
|
||||||
|
|
||||||
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());
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
constexpr i16 VERSION = 1;
|
constexpr i16 VERSION = 1;
|
||||||
constexpr u64 AFTER_HEADER_OFFSET = 6;
|
constexpr u64 AFTER_HEADER_OFFSET = 22;
|
||||||
|
|
||||||
struct FileHeader {
|
struct FileHeader {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|||||||
Reference in New Issue
Block a user