Migrate from GitHub
This commit is contained in:
97 files changed
+8272
No files matched your search
Submodule
+1
Submodule lib/Arduino-Base32-Decode added at f071869f5d.
Submodule
+1
Submodule lib/Callback added at 6565b0a8a0.
Submodule
+1
Submodule lib/ESP32-BLE-CompositeHID added at 06d93eab49.
@@ -0,0 +1,286 @@
|
||||
#include "cryptographic_operations.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/gcm.h>
|
||||
#include <mbedtls/pkcs5.h>
|
||||
#include <esp_system.h>
|
||||
|
||||
namespace pvault_cryptography {
|
||||
bool generate_salt(uint8_t* salt) {
|
||||
if(salt == nullptr) { return false; }
|
||||
esp_fill_random(salt, pvault::salt_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool generate_nonce(uint8_t* nonce) {
|
||||
if(nonce == nullptr) { return false; }
|
||||
esp_fill_random(nonce, pvault::nonce_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool derive_key(const String& password, const uint8_t* salt, uint8_t* key) {
|
||||
// argument validation
|
||||
if(salt == nullptr || key == nullptr) { return false; }
|
||||
|
||||
// init ESP32 cryptography
|
||||
mbedtls_md_context_t md_ctx;
|
||||
mbedtls_md_init(&md_ctx);
|
||||
|
||||
// SHA256 algorithm descriptions
|
||||
const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
if(md_info == nullptr) { return false; }
|
||||
|
||||
// create HMAC context
|
||||
int result = mbedtls_md_setup(&md_ctx, md_info, 1);
|
||||
if(result != 0) {
|
||||
mbedtls_md_free(&md_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate AES128 key from provided password
|
||||
result = mbedtls_pkcs5_pbkdf2_hmac(
|
||||
&md_ctx,
|
||||
reinterpret_cast<const unsigned char*>(password.c_str()),
|
||||
password.length(),
|
||||
salt,
|
||||
pvault::salt_size,
|
||||
pvault::iterations,
|
||||
pvault::key_size,
|
||||
key
|
||||
);
|
||||
|
||||
// free HMAC context
|
||||
mbedtls_md_free(&md_ctx);
|
||||
|
||||
if(result != 0) {
|
||||
secure_zero(key, pvault::key_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decrypt_verify(
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
File& file,
|
||||
const uint32_t ciphertext_length,
|
||||
const uint8_t* tag,
|
||||
const uint32_t header_size
|
||||
) {
|
||||
// argument validation
|
||||
if(!key || !nonce || !file || !tag) { return false; }
|
||||
|
||||
// GCM init
|
||||
mbedtls_gcm_context gcm;
|
||||
mbedtls_gcm_init(&gcm);
|
||||
|
||||
int ret = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, 128);
|
||||
if(ret != 0) {
|
||||
mbedtls_gcm_free(&gcm);
|
||||
return false;
|
||||
}
|
||||
|
||||
file.seek(header_size);
|
||||
|
||||
// buffers
|
||||
uint8_t chunk_bytes[CHUNK_SIZE];
|
||||
uint32_t bytes_remaining = ciphertext_length;
|
||||
|
||||
mbedtls_gcm_starts(&gcm, MBEDTLS_GCM_DECRYPT, nonce, pvault::nonce_size, NULL, 0);
|
||||
|
||||
uint32_t read_size;
|
||||
|
||||
while(bytes_remaining > 0) {
|
||||
if(bytes_remaining > CHUNK_SIZE) {
|
||||
read_size = CHUNK_SIZE;
|
||||
} else {
|
||||
read_size = bytes_remaining;
|
||||
}
|
||||
|
||||
file.read(chunk_bytes, read_size);
|
||||
|
||||
mbedtls_gcm_update(&gcm, read_size, chunk_bytes, chunk_bytes);
|
||||
|
||||
bytes_remaining -= read_size;
|
||||
}
|
||||
|
||||
uint8_t computed_tag[pvault::tag_size];
|
||||
mbedtls_gcm_finish(&gcm, computed_tag, pvault::tag_size);
|
||||
mbedtls_gcm_free(&gcm);
|
||||
|
||||
return memcmp(tag, computed_tag, pvault::tag_size) == 0;
|
||||
}
|
||||
|
||||
bool encrypt_chunked(
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
const uint8_t* plaintext,
|
||||
uint32_t plaintext_length,
|
||||
File& file
|
||||
) {
|
||||
// argument validation
|
||||
if(!key || !nonce || !plaintext || !file) { return false; }
|
||||
|
||||
// buffers
|
||||
uint8_t tag[pvault::tag_size];
|
||||
uint8_t chunk_bytes[CHUNK_SIZE];
|
||||
|
||||
// GCM init
|
||||
mbedtls_gcm_context gcm;
|
||||
mbedtls_gcm_init(&gcm);
|
||||
|
||||
int ret = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, 128);
|
||||
if(ret != 0) {
|
||||
mbedtls_gcm_free(&gcm);
|
||||
return false;
|
||||
}
|
||||
|
||||
// encryption
|
||||
mbedtls_gcm_starts(&gcm, MBEDTLS_GCM_ENCRYPT, nonce, pvault::nonce_size, NULL, 0);
|
||||
|
||||
uint32_t remaining = plaintext_length;
|
||||
uint32_t read_offset = 0;
|
||||
|
||||
while(remaining > 0) {
|
||||
uint32_t n;
|
||||
|
||||
if(remaining > CHUNK_SIZE) {
|
||||
n = CHUNK_SIZE;
|
||||
} else {
|
||||
n = remaining;
|
||||
}
|
||||
|
||||
mbedtls_gcm_update(&gcm, n, plaintext + read_offset, chunk_bytes);
|
||||
file.write(chunk_bytes, n);
|
||||
|
||||
read_offset += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
mbedtls_gcm_finish(&gcm, tag, pvault::tag_size);
|
||||
mbedtls_gcm_free(&gcm);
|
||||
file.write(tag, pvault::tag_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decrypt_chunked( // will return junk if get_key earlier returned false but program still decided to decrypt
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
File& file,
|
||||
uint32_t ciphertext_length,
|
||||
uint8_t* plaintext
|
||||
) {
|
||||
// argument validation
|
||||
if(!key || !nonce || !file || !plaintext) { return false; }
|
||||
|
||||
// buffers
|
||||
uint32_t file_size = file.size();
|
||||
uint8_t tag[pvault::tag_size];
|
||||
uint8_t chunk_bytes[CHUNK_SIZE];
|
||||
|
||||
// read tag
|
||||
file.seek(file_size - pvault::tag_size);
|
||||
file.read(tag, pvault::tag_size);
|
||||
|
||||
// GCM init
|
||||
mbedtls_gcm_context gcm;
|
||||
mbedtls_gcm_init(&gcm);
|
||||
|
||||
int ret = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, 128);
|
||||
if(ret != 0) {
|
||||
mbedtls_gcm_free(&gcm);
|
||||
return false;
|
||||
}
|
||||
|
||||
// decryption
|
||||
mbedtls_gcm_starts(&gcm, MBEDTLS_GCM_DECRYPT, nonce, pvault::nonce_size, NULL, 0);
|
||||
|
||||
// seek back to ciphertext start
|
||||
file.seek(sizeof(pvault::header));
|
||||
|
||||
uint32_t bytes_remaining = ciphertext_length;
|
||||
uint32_t write_offset = 0;
|
||||
uint32_t read_size;
|
||||
|
||||
while(bytes_remaining > 0) {
|
||||
if(bytes_remaining > CHUNK_SIZE) {
|
||||
read_size = CHUNK_SIZE;
|
||||
} else {
|
||||
read_size = bytes_remaining;
|
||||
}
|
||||
|
||||
file.read(chunk_bytes, read_size);
|
||||
|
||||
mbedtls_gcm_update(&gcm, read_size, chunk_bytes, plaintext + write_offset);
|
||||
|
||||
write_offset += read_size;
|
||||
bytes_remaining -= read_size;
|
||||
}
|
||||
|
||||
mbedtls_gcm_finish(&gcm, tag, pvault::tag_size);
|
||||
mbedtls_gcm_free(&gcm);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_header(File& file, const pvault::header& hdr) {
|
||||
// file validation
|
||||
if(!file) { return false; }
|
||||
|
||||
// write header
|
||||
uint32_t written = file.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(pvault::header));
|
||||
|
||||
// validate written data
|
||||
if(written != sizeof(pvault::header)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_header(File& file, pvault::header& hdr) {
|
||||
// file validation
|
||||
if(!file) { return false; }
|
||||
|
||||
// read binary header
|
||||
uint32_t read_bytes = file.read(reinterpret_cast<uint8_t*>(&hdr), sizeof(pvault::header));
|
||||
|
||||
// validate read data
|
||||
if(read_bytes != sizeof(pvault::header)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_tag(File& file, const uint8_t* tag) {
|
||||
// file and tag validation
|
||||
if(!file || !tag) { return false; }
|
||||
|
||||
// write binary tag
|
||||
uint32_t written = file.write(tag, pvault::tag_size);
|
||||
|
||||
// validate written data
|
||||
if(written != pvault::tag_size) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_tag(File& file, uint8_t* tag) {
|
||||
// file and tag validation
|
||||
if(!file || !tag) { return false; }
|
||||
|
||||
// read binary tag
|
||||
uint32_t read_bytes = file.read(tag, pvault::tag_size);
|
||||
|
||||
// validate read data
|
||||
if(read_bytes != pvault::tag_size) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void secure_zero(void* ptr, uint32_t size) {
|
||||
volatile uint8_t* p = reinterpret_cast<volatile uint8_t*>(ptr);
|
||||
|
||||
while(size--) {
|
||||
*p++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
|
||||
#include "pvault.h"
|
||||
|
||||
#define CHUNK_SIZE 1024 // bytes
|
||||
|
||||
namespace pvault_cryptography {
|
||||
// generate values
|
||||
bool generate_salt(uint8_t* salt);
|
||||
bool generate_nonce(uint8_t* nonce);
|
||||
|
||||
// cryptography
|
||||
bool derive_key(const String& password, const uint8_t* salt, uint8_t* key); // Derive key from password
|
||||
|
||||
bool decrypt_verify( // lightweight function to check if used key works with the ciphertext
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
File& file,
|
||||
const uint32_t ciphertext_length,
|
||||
const uint8_t* tag,
|
||||
uint32_t header_size
|
||||
);
|
||||
|
||||
bool encrypt_chunked( // writes ciphertext + tag to provided file
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
const uint8_t* plaintext,
|
||||
uint32_t plaintext_length,
|
||||
File& file
|
||||
);
|
||||
|
||||
bool decrypt_chunked( // reads tag itself + plaintext to provided pointer
|
||||
const uint8_t* key,
|
||||
const uint8_t* nonce,
|
||||
File& file,
|
||||
uint32_t ciphertext_length,
|
||||
uint8_t* plaintext
|
||||
);
|
||||
|
||||
// vault file structure
|
||||
bool write_header(File& file, const pvault::header& hdr);
|
||||
bool read_header(File& file, pvault::header& hdr); // assumes that file pointer is set at the beginning of the file
|
||||
|
||||
bool write_tag(File& file, const uint8_t* tag);
|
||||
bool read_tag(File& file, uint8_t* tag); // assumes that file pointer is set on tag's location
|
||||
|
||||
// memory security
|
||||
void secure_zero(void* ptr, uint32_t size);
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
#include "pvault.h"
|
||||
#include "cryptographic_operations.h"
|
||||
|
||||
namespace pvault {
|
||||
bool get_key(
|
||||
const char* path,
|
||||
const String& password,
|
||||
uint8_t* key
|
||||
) {
|
||||
File file = SD.open(path, FILE_READ);
|
||||
if(!file) { return false; }
|
||||
|
||||
// get master salt and nonce
|
||||
header hdr{};
|
||||
pvault_cryptography::read_header(file, hdr);
|
||||
|
||||
// obtain key candidate
|
||||
pvault_cryptography::derive_key(password, hdr.master_salt, key);
|
||||
|
||||
uint32_t len = hdr.ciphertext_length;
|
||||
|
||||
// get tag
|
||||
uint8_t tag[pvault::tag_size];
|
||||
uint32_t file_size = file.size();
|
||||
file.seek(file_size - pvault::tag_size);
|
||||
file.read(tag, pvault::tag_size);
|
||||
|
||||
bool decryption = pvault_cryptography::decrypt_verify(key, hdr.nonce, file, len, tag, sizeof(pvault::header));
|
||||
|
||||
file.close();
|
||||
|
||||
return decryption;
|
||||
}
|
||||
|
||||
bool init_vault(
|
||||
const char* path,
|
||||
const String& password,
|
||||
const device_settings& settings,
|
||||
const vault& data
|
||||
) {
|
||||
// input validation
|
||||
if(!path || password.length() == 0) { return false; }
|
||||
|
||||
// open file
|
||||
if(SD.exists(path)) { SD.remove(path); }
|
||||
File file = SD.open(path, FILE_WRITE);
|
||||
if(!file) { return false; }
|
||||
|
||||
// buffers
|
||||
header hdr{};
|
||||
uint8_t salt[salt_size];
|
||||
uint8_t nonce[nonce_size];
|
||||
uint8_t key[key_size];
|
||||
uint32_t len = sizeof(data);
|
||||
|
||||
// generate parameters
|
||||
pvault_cryptography::generate_salt(salt);
|
||||
pvault_cryptography::generate_nonce(nonce);
|
||||
pvault_cryptography::derive_key(password, salt, key);
|
||||
|
||||
// build header
|
||||
hdr.magic[0] = 'C';
|
||||
hdr.magic[1] = 'R';
|
||||
hdr.magic[2] = 'Y';
|
||||
hdr.magic[3] = 'P';
|
||||
|
||||
hdr.version = file_version;
|
||||
hdr.iterations = iterations;
|
||||
|
||||
memcpy(hdr.master_salt, salt, salt_size);
|
||||
memcpy(hdr.nonce, nonce, nonce_size);
|
||||
|
||||
hdr.settings = settings;
|
||||
|
||||
hdr.ciphertext_length = len;
|
||||
|
||||
pvault_cryptography::write_header(file, hdr);
|
||||
|
||||
// encrypt
|
||||
bool encryption = pvault_cryptography::encrypt_chunked(
|
||||
key,
|
||||
nonce,
|
||||
reinterpret_cast<const uint8_t*>(&data),
|
||||
len,
|
||||
file
|
||||
);
|
||||
|
||||
// cleanup
|
||||
pvault_cryptography::secure_zero(salt, salt_size);
|
||||
pvault_cryptography::secure_zero(nonce, nonce_size);
|
||||
pvault_cryptography::secure_zero(key, key_size);
|
||||
file.close();
|
||||
|
||||
return encryption;
|
||||
}
|
||||
|
||||
bool update_vault(
|
||||
const char* path,
|
||||
const uint8_t* key,
|
||||
const device_settings& settings,
|
||||
const vault& data
|
||||
) {
|
||||
// input validation
|
||||
if(!path || !key) { return false; }
|
||||
|
||||
// open file
|
||||
File read = SD.open(path, FILE_READ);
|
||||
if(!read) { return false; }
|
||||
|
||||
// read header
|
||||
header hdr{};
|
||||
if(!pvault_cryptography::read_header(read, hdr)) {
|
||||
read.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// close file (we have everything needed)
|
||||
read.close();
|
||||
|
||||
// write header and encrypted data
|
||||
SD.remove(path);
|
||||
File write = SD.open(path, FILE_WRITE);
|
||||
if(!write) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate new nonce
|
||||
pvault_cryptography::generate_nonce(hdr.nonce);
|
||||
|
||||
// modify header
|
||||
hdr.settings = settings;
|
||||
|
||||
pvault_cryptography::write_header(write, hdr);
|
||||
|
||||
// encrypt
|
||||
uint32_t len = sizeof(data);
|
||||
|
||||
bool encryption = pvault_cryptography::encrypt_chunked(
|
||||
key,
|
||||
hdr.nonce,
|
||||
reinterpret_cast<const uint8_t*>(&data),
|
||||
len,
|
||||
write
|
||||
);
|
||||
|
||||
// cleanup
|
||||
write.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_vault(
|
||||
const char* path,
|
||||
const uint8_t* key,
|
||||
device_settings& settings,
|
||||
vault& data
|
||||
) {
|
||||
// input validation
|
||||
if(!path || !key) { return false; }
|
||||
|
||||
// open file
|
||||
File file = SD.open(path, FILE_READ);
|
||||
if(!file) { return false; }
|
||||
|
||||
// read header
|
||||
header hdr{};
|
||||
if(!pvault_cryptography::read_header(file, hdr)) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// header validation
|
||||
if(hdr.magic[0] != 'C' || hdr.magic[1] != 'R' || hdr.magic[2] != 'Y' || hdr.magic[3] != 'P') {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(hdr.version != file_version) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(hdr.ciphertext_length == 0) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
settings = hdr.settings;
|
||||
|
||||
// buffers
|
||||
uint32_t len = hdr.ciphertext_length;
|
||||
uint8_t nonce[nonce_size];
|
||||
|
||||
// parameters
|
||||
memcpy(nonce, hdr.nonce, nonce_size);
|
||||
|
||||
bool decryption = pvault_cryptography::decrypt_chunked(
|
||||
key,
|
||||
nonce,
|
||||
file,
|
||||
len,
|
||||
reinterpret_cast<uint8_t*>(&data)
|
||||
);
|
||||
|
||||
// cleanup
|
||||
file.close();
|
||||
return decryption;
|
||||
}
|
||||
|
||||
bool update_config(
|
||||
const char* path,
|
||||
const device_settings& settings
|
||||
) {
|
||||
// open the old file
|
||||
File old_file = SD.open(path, FILE_READ);
|
||||
if(!old_file) { return false; }
|
||||
|
||||
// read its header
|
||||
header hdr{};
|
||||
if(!pvault_cryptography::read_header(old_file, hdr)) {
|
||||
old_file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// update settings in header
|
||||
hdr.settings = settings;
|
||||
|
||||
// obtain length and add tag size to it
|
||||
uint32_t len = hdr.ciphertext_length;
|
||||
len += pvault::tag_size;
|
||||
|
||||
// open temp file
|
||||
String temp_path = String(path);
|
||||
temp_path += ".temp";
|
||||
|
||||
if(SD.exists(temp_path)) { SD.remove(temp_path); }
|
||||
File new_file = SD.open(temp_path, FILE_WRITE);
|
||||
if(!new_file) {
|
||||
old_file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// rewrite header and remaining bytes
|
||||
if(!pvault_cryptography::write_header(new_file, hdr)) {
|
||||
new_file.close();
|
||||
old_file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t to_write = len;
|
||||
uint32_t write_size;
|
||||
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
|
||||
while(to_write > 0) {
|
||||
if(to_write > CHUNK_SIZE) {
|
||||
write_size = CHUNK_SIZE;
|
||||
} else {
|
||||
write_size = to_write;
|
||||
}
|
||||
|
||||
old_file.read(buffer, write_size);
|
||||
new_file.write(buffer, write_size);
|
||||
|
||||
to_write -= write_size;
|
||||
}
|
||||
|
||||
old_file.close();
|
||||
new_file.close();
|
||||
|
||||
// delete old file and replace with new
|
||||
SD.remove(path);
|
||||
if(SD.exists(temp_path)) { SD.rename(temp_path, path); }
|
||||
|
||||
// exit
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_config(
|
||||
const char* path,
|
||||
device_settings& settings
|
||||
) {
|
||||
File file = SD.open(path, FILE_READ);
|
||||
if(!file) { return false; }
|
||||
|
||||
header hdr{};
|
||||
pvault_cryptography::read_header(file, hdr);
|
||||
|
||||
file.close();
|
||||
|
||||
settings = hdr.settings;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_salt(
|
||||
const char* path,
|
||||
uint8_t* salt
|
||||
) {
|
||||
if (!path) { return false; }
|
||||
|
||||
File file = SD.open(path, FILE_READ);
|
||||
if(!file) { return false; }
|
||||
header hdr{};
|
||||
if(!pvault_cryptography::read_header(file, hdr)) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(salt, hdr.master_salt, pvault::salt_size);
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replace_vault(
|
||||
const char* path,
|
||||
const device_settings& settings,
|
||||
|
||||
const uint8_t* key,
|
||||
const uint8_t* salt,
|
||||
|
||||
const char* import_path
|
||||
) {
|
||||
File import_file = SD.open(import_path, FILE_READ);
|
||||
// read file header
|
||||
// magic 8 bytes
|
||||
uint8_t magic[8];
|
||||
import_file.read(magic, 8);
|
||||
|
||||
if(memcmp(magic, "PWIMPORT", 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// nonce 12 bytes
|
||||
uint8_t nonce[pvault::nonce_size];
|
||||
import_file.read(nonce, pvault::nonce_size);
|
||||
|
||||
// length 4 bytes
|
||||
uint32_t len;
|
||||
import_file.read(reinterpret_cast<uint8_t*>(&len), sizeof(uint32_t));
|
||||
|
||||
// read tag
|
||||
uint32_t file_size = import_file.size();
|
||||
uint8_t tag[pvault::tag_size];
|
||||
import_file.seek(file_size - pvault::tag_size);
|
||||
import_file.read(tag, pvault::tag_size);
|
||||
|
||||
// check if key that we have can decrypt the imported vault. Otherwise there's no point doing it.
|
||||
bool decryption = pvault_cryptography::decrypt_verify(key, nonce, import_file, len, tag, 24);
|
||||
if(!decryption) { return false; }
|
||||
|
||||
// overwrite vault with provided settings
|
||||
header hdr{};
|
||||
|
||||
hdr.magic[0] = 'C';
|
||||
hdr.magic[1] = 'R';
|
||||
hdr.magic[2] = 'Y';
|
||||
hdr.magic[3] = 'P';
|
||||
|
||||
hdr.iterations = pvault::iterations;
|
||||
hdr.settings = settings;
|
||||
|
||||
hdr.ciphertext_length = len;
|
||||
hdr.version = pvault::file_version;
|
||||
memcpy(hdr.master_salt, salt, pvault::salt_size);
|
||||
memcpy(hdr.nonce, nonce, pvault::nonce_size);
|
||||
|
||||
if(SD.exists(path)) { SD.remove(path); }
|
||||
File new_file = SD.open(path, FILE_WRITE);
|
||||
|
||||
pvault_cryptography::write_header(new_file, hdr);
|
||||
|
||||
// copy ciphertext from old file to new
|
||||
uint32_t to_copy = len;
|
||||
uint32_t copy_size;
|
||||
uint8_t copy_buffer[CHUNK_SIZE];
|
||||
|
||||
import_file.seek(24);
|
||||
|
||||
while(to_copy > 0) {
|
||||
if(to_copy > CHUNK_SIZE) {
|
||||
copy_size = CHUNK_SIZE;
|
||||
} else {
|
||||
copy_size = to_copy;
|
||||
}
|
||||
|
||||
import_file.read(copy_buffer, copy_size);
|
||||
new_file.write(copy_buffer, copy_size);
|
||||
|
||||
to_copy -= copy_size;
|
||||
}
|
||||
|
||||
pvault_cryptography::write_tag(new_file, tag);
|
||||
|
||||
import_file.close();
|
||||
new_file.close();
|
||||
|
||||
// remove import file
|
||||
SD.remove(import_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
|
||||
// PWDer vault file format 1.0
|
||||
// and functions for its operation
|
||||
// let's see
|
||||
|
||||
namespace pvault {
|
||||
constexpr uint8_t magic_size = 4;
|
||||
constexpr uint8_t file_version = 1;
|
||||
|
||||
constexpr uint32_t iterations = 10000;
|
||||
constexpr uint8_t salt_size = 16;
|
||||
constexpr uint8_t nonce_size = 12;
|
||||
|
||||
constexpr uint8_t key_size = 16;
|
||||
constexpr uint8_t tag_size = 16;
|
||||
|
||||
constexpr uint16_t max_entries = 101; // first is used for configuration
|
||||
constexpr uint16_t max_length = 128;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// encrypted data
|
||||
struct credential {
|
||||
char title[max_length];
|
||||
char username[max_length];
|
||||
char password[max_length];
|
||||
char totp_secret[max_length];
|
||||
};
|
||||
|
||||
struct vault {
|
||||
uint16_t credential_count;
|
||||
credential credentials[max_entries];
|
||||
};
|
||||
|
||||
// unencrypted settings in header
|
||||
struct device_settings {
|
||||
uint8_t speaker_on_auto_lock; // speaker on bit, then auto lock bit
|
||||
uint8_t input_mode;
|
||||
uint8_t brightness;
|
||||
uint8_t color_scheme;
|
||||
};
|
||||
|
||||
// file header
|
||||
struct header {
|
||||
char magic[magic_size];
|
||||
uint8_t version;
|
||||
|
||||
uint32_t iterations;
|
||||
uint8_t master_salt[salt_size];
|
||||
uint8_t nonce[nonce_size];
|
||||
|
||||
device_settings settings;
|
||||
|
||||
uint32_t ciphertext_length;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// surface-level functions
|
||||
bool get_key( // returns false if password is incorrect
|
||||
const char* path,
|
||||
const String& password,
|
||||
uint8_t* key
|
||||
);
|
||||
|
||||
bool init_vault( // create new vault using provided password
|
||||
const char* path,
|
||||
const String& password,
|
||||
const device_settings& settings,
|
||||
const vault& data
|
||||
);
|
||||
bool update_vault( // update vault data with new information
|
||||
const char* path,
|
||||
const uint8_t* key,
|
||||
const device_settings& settings,
|
||||
const vault& data
|
||||
);
|
||||
bool load_vault( // load existing vault to memory
|
||||
const char* path,
|
||||
const uint8_t* key,
|
||||
device_settings& settings,
|
||||
vault& data
|
||||
);
|
||||
|
||||
bool update_config( // change unencrypted config without touching the vault data
|
||||
const char* path,
|
||||
const device_settings& settings
|
||||
);
|
||||
bool read_config( // read config without need to decrypt the entire file
|
||||
const char* path,
|
||||
device_settings& settings
|
||||
);
|
||||
|
||||
bool get_salt( // because we need to give it to the PC if we're importing passwords
|
||||
const char* path,
|
||||
uint8_t* salt
|
||||
);
|
||||
bool replace_vault( // with import file
|
||||
const char* path,
|
||||
const device_settings& settings,
|
||||
|
||||
const uint8_t* key,
|
||||
const uint8_t* salt,
|
||||
|
||||
const char* import_path
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user