Commit b75d8db8 authored by Joel Copi's avatar Joel Copi
Browse files

Merge branch 'archive' into 'master'

Archive

Closes #1

See merge request !1
parents ea9ffa9f dd5f3179
[submodule "hashmap"]
path = hashmap
url = git@gitlab.copi.dev:jcopi/hashmap.git
# passmngr
Data will be organized into seperate files:
- A `keys` file will hold encryption keys for decrypting other files. The keys file will have a salt attached somewhere in the meta data that will be used to create the master encryption key from the user password. This will be verified as associated data in the AEAD used to secure the keys file.
- An encrypted `accounts` file will hold information on user accounts. This will include domains/app identifiers, usernames, passwords and other meta data to facilitate searching.
- An encrypted `settings` file will hold user preferences. password timeout settings, master password keygen parameters, etc.
The initial MVP will not use libsodium allocations, this will bring the risk of sensitive data being paged to RAM, testing will be done to determine the best way to utilize the limited amount of non-paged ram available.
\ No newline at end of file
Specification and implementation in progress
#include "archive.h"
#include <stdlib.h>
#define ARCHIVE_ITEM_NAME_MAXBYTES (128)
result_t(archive_t, archive_error_t) archive_create(const char* name)
{
}
int archive_init (struct archive* ar)
{
if (hm_init(&ar->entities) != 0) {
return -1;
}
ar->entities.deallocator = free;
ar->file_stream = NULL;
return 0;
}
/*int archive_create (struct archive* ar, const char* name)
{
ar->mode = WRITE;
ar->file_stream = fopen(name, "wb");
if (ar->file_stream == NULL) {
return -1;
}
return 0;
}
int archive_open (struct archive* ar, const char* name)
{
ar->mode = READ;
ar->file_stream = fopen(name, "rb");
if (ar->file_stream == NULL) {
return -1;
}
return archive_populate_items(ar);
}*/
static int archive_populate_items (struct archive* ar)
{
size_t rlen;
uint16_t name_bytes;
uint64_t item_bytes;
size_t hstart;
int eof;
do {
hstart = ftell(ar->file_stream);
rlen = fread(&name_bytes, sizeof (name_bytes), 1, ar->file_stream);
eof = feof(ar->file_stream);
if (rlen != sizeof (name_bytes) || eof || name_bytes > ARCHIVE_ITEM_NAME_MAXBYTES) {
return -1;
}
char* item_name = calloc(name_bytes, sizeof (char));
if (item_name == NULL) {
return -1;
}
rlen = fread(item_name, sizeof (char), name_bytes, ar->file_stream);
eof = feof(ar->file_stream);
if (rlen != name_bytes || eof) {
free(item_name);
return -1;
}
rlen = fread(&item_bytes, sizeof (item_bytes), 1, ar->file_stream);
eof = feof(ar->file_stream);
if (rlen != sizeof (item_bytes) || (eof && item_bytes != 0)) {
free(item_name);
return -1;
}
struct archive_entity* entity = calloc(sizeof (struct archive_entity), 1);
if (entity == NULL) {
free(item_name);
return -1;
}
entity->start = ftell(ar->file_stream);
entity->header_start = hstart;
entity->length = item_bytes;
if (hm_set(&ar->entities, item_name, entity) != 0) {
free(item_name);
free(entity);
return -1;
}
if (fseek(ar->file_stream, item_bytes, SEEK_CUR) != 0) {
return -1;
}
eof = feof(ar->file_stream);
} while (!eof);
return 0;
}
int archive_close (struct archive* ar)
{
fclose(ar->file_stream);
ar->file_stream = NULL;
hm_destroy(&ar->entities);
return 0;
}
int archive_create_item (struct archive* ar, const void* name, size_t name_bytes)
{
if (ar->mode != WRITE || name_bytes > UINT16_MAX) {
return -1;
}
struct archive_entity* entity = calloc(1, sizeof (struct archive_entity));
if (entity == NULL) {
return -1;
}
entity->header_start = ftell(ar->file_stream);
uint16_t nbytes = name_bytes;
if (fwrite(&nbytes, sizeof (nbytes), 1, ar->file_stream) != sizeof (nbytes)) {
return -1;
}
if (fwrite(name, 1, name_bytes, ar->file_stream) != name_bytes) {
return -1;
}
uint64_t content_bytes = 0;
if (fwrite(&content_bytes, sizeof (content_bytes), 1, ar->file_stream) != sizeof (content_bytes)) {
return -1;
}
entity->start = ftell(ar->file_stream);
entity->length = 0;
hm_set_copy(&ar->entities, name, name_bytes, entity);
}
int archive_write_item (struct archive* ar, void* buffer, size_t buffer_bytes);
int archive_close_item (struct archive* ar);
int archive_open_item (struct archive* ar, const unsigned char* name, size_t name_bytes);
int archive_read_item (struct archive* ar, void* buffer, size_t buffer_bytes);
int archive_close_item (struct archive* ar);
\ No newline at end of file
#ifndef __PASSMNGR_ARCHIVE_H
#define __PASSMNGR_ARCHIVE_H
#include "hashmap/hashmap.h"
#include "result.h"
#include <stdlib.h>
#include <stdbool.h>
struct archive_entity {
size_t start;
size_t header_start;
uint64_t length;
};
enum archive_mode {
READ,
WRITE,
};
enum archive_state {
IDLE,
ARCHIVE_OPEN,
ITEM_OPEN,
};
typedef enum archive_error {
READ_FAILED,
WRITE_FAILED,
SEEK_FAILED,
ALLOCATION_FAILED,
} archive_error_t;
typedef struct archive {
map_t entities;
FILE file_stream;
enum archive_mode mode;
enum archive_state state;
} archive_t;
result_t_init(archive_t, archive_error_t);
result_t_init(nil_t, archive_error_t);
result_t(archive_t, archive_error_t) archive_create(const char* name);
result_t(archive_t, archive_error_t) archive_open (const char* name);
result_t(nil_t, archive_error_t) archive_close (archive_t* a);
// int archive_init (struct archive* ar);
//int archive_create (struct archive* ar, const char* name);
//int archive_open (struct archive* ar, const char* name);
// int archive_close (struct archive* ar);
int archive_create_item (struct archive* ar, const void* name, size_t name_bytes);
int archive_write_item (struct archive* ar, void* buffer, size_t buffer_bytes);
int archive_close_item (struct archive* ar);
int archive_open_item (struct archive* ar, const void* name, size_t name_bytes);
int archive_read_item (struct archive* ar, void* buffer, size_t buffer_bytes);
int archive_close_item (struct archive* ar);
static int archive_populate_items (struct archive* ar);
#endif
\ No newline at end of file
#include <string.h>
#include <stdlib.h>
#include "archive.h"
#define MIN(A,B) ((A) < (B) ? (A) : (B))
#define MAX(A,B) ((A) > (B) ? (A) : (B))
static info_ref_result_t archive_read_info (archive_t* ar, size_t remaining_bytes);
static size_result_t archive_write_info (archive_t* ar, archive_item_info_t* info);
static empty_result_t archive_parse_index (archive_t* ar);
static empty_result_t archive_write_index (archive_t* ar);
static size_result_t archive_find_item (archive_t* ar, const byte_t* name, NAME_SIZE_TYPE name_bytes);
archive_result_t archive_open (const char* file_name, archive_mode_t mode)
{
archive_t ar = {0};
archive_result_t result = {0};
switch (mode) {
case READ:
{
ar.file = fopen(file_name, "rb");
if (ar.file == NULL) {
return ERR(result, FILE_OPEN_FAILED);
}
ar.opened = true;
ar.mode = READ;
empty_result_t er = archive_parse_index(&ar);
if (IS_ERR(er)) {
return ERR(result, UNWRAP_ERR(er));
}
return OK(result, ar);
}
case WRITE:
{
ar.file = fopen(file_name, "wb");
if (ar.file == NULL) {
return ERR(result, FILE_OPEN_FAILED);
}
ar.opened = true;
ar.mode = WRITE;
return OK(result, ar);
}
}
}
empty_result_t archive_close (archive_t* ar)
{
assert(ar->opened == true);
assert(ar->locked == false);
empty_result_t result = {0};
switch (ar->mode) {
case READ: break;
case WRITE:
{
empty_result_t er = archive_write_index(ar);
if (IS_ERR(er)) {
return ERR(result, UNWRAP_ERR(er));
}
break;
}
}
fclose(ar->file);
for (size_t i = 0; i < ar->item_count; i++) {
free(ar->items[i]);
}
free(ar->items);
*ar = (archive_t) {0};
return OK_EMPTY(result);
}
item_result_t archive_item_open (archive_t* ar, const byte_t* name, NAME_SIZE_TYPE name_bytes)
{
assert(ar->opened == true);
assert(ar->locked == false);
archive_item_t item = {0};
item_result_t result = {0};
switch (ar->mode) {
case READ:
{
assert(ar->indexed == true);
size_result_t sr = archive_find_item(ar, name, name_bytes);
if (IS_ERR(sr)) {
return ERR(result, UNWRAP_ERR(sr));
}
size_t i = UNWRAP(sr);
item = (archive_item_t){
.parent = ar,
.info = ar->items[i],
.current = 0
};
if (fseek(ar->file, item.info->start, SEEK_SET) != 0) {
return ERR(result, FILE_SEEK_FAILED);
}
ar->locked = true;
return OK(result, item);
}
case WRITE:
{
size_result_t sr = archive_find_item(ar, name, name_bytes);
if (IS_OK(sr)) {
return ERR(result, RUNTIME_ITEM_PREV_OPENED);
}
archive_item_info_t* info = malloc(sizeof (archive_item_info_t) + name_bytes);
if (info == NULL) {
return ERR(result, FATAL_OUT_OF_MEMORY);
}
memcpy(info->name, name, name_bytes);
info->name_bytes = name_bytes;
info->start = ar->items_bytes;
info->bytes = 0;
size_t new_item_count = ar->item_count + 1;
archive_item_info_t** tmp = realloc(ar->items, sizeof (*tmp) * new_item_count);
if (tmp == NULL) {
free(info);
return ERR(result, FATAL_OUT_OF_MEMORY);
}
size_t i = ar->item_count;
ar->items = tmp;
ar->items[ar->item_count] = info;
ar->item_count = new_item_count;
item = (archive_item_t) {
.parent = ar,
.info = ar->items[i],
.current = 0,
};
if (fseek(ar->file, 0, SEEK_END) != 0) {
return ERR(result, FILE_SEEK_FAILED);
}
ar->locked = true;
return OK(result, item);
}
}
}
size_result_t archive_item_read (archive_item_t* item, byte_t* buffer, size_t buffer_bytes)
{
assert(item->parent->opened == true);
assert(item->parent->mode == READ);
assert(item->parent->locked == true);
assert(item->parent->indexed == true);
size_t bytes_to_read;
size_result_t result = {0};
size_t bytes_remaining = item->info->bytes - item->current;
bytes_to_read = MIN(bytes_remaining, buffer_bytes);
if (fread(buffer, 1, bytes_to_read, item->parent->file) != bytes_to_read) {
return ERR(result, FILE_READ_FAILED);
}
return OK(result, bytes_to_read);
}
size_result_t archive_item_write (archive_item_t* item, const byte_t* buffer, size_t buffer_bytes)
{
assert(item->parent->opened == true);
assert(item->parent->mode == WRITE);
assert(item->parent->locked == true);
size_result_t result = {0};
if (fwrite(buffer, 1, buffer_bytes, item->parent->file) != buffer_bytes) {
return ERR(result, FILE_WRITE_FAILED);
}
item->current += buffer_bytes;
item->info->bytes += buffer_bytes;
item->parent->items_bytes += buffer_bytes;
return OK(result, buffer_bytes);
}
empty_result_t archive_item_close (archive_item_t* item)
{
assert(item->parent->opened == true);
assert(item->parent->locked == true);
empty_result_t result = {0};
item->parent->locked = false;
if (fflush(item->parent->file) != 0) {
return ERR(result, FILE_WRITE_FAILED);
}
item->parent = NULL;
item->info = NULL;
item->current = 0;
return OK_EMPTY(result);
}
static info_ref_result_t archive_read_info (archive_t* ar, size_t remaining_bytes)
{
assert(ar->opened == true);
assert(ar->mode == READ);
assert(ar->locked == false);
info_ref_result_t result = {0};
NAME_SIZE_TYPE name_bytes = 0;
if (remaining_bytes <= sizeof (name_bytes)) {
return ERR(result, FATAL_INDEX_MALFORMED);
}
if (fread(&name_bytes, sizeof (name_bytes), 1, ar->file) != 1) {
return ERR(result, FILE_READ_FAILED);
}
remaining_bytes -= sizeof (name_bytes);
if (remaining_bytes < name_bytes + sizeof (ITEM_START_TYPE) + sizeof (ITEM_SIZE_TYPE)) {
return ERR(result, FATAL_INDEX_MALFORMED);
}
archive_item_info_t* info = malloc(sizeof (archive_item_info_t) + name_bytes);
if (info == NULL) {
return ERR(result, FATAL_OUT_OF_MEMORY);
}
info->name_bytes = name_bytes;
if (fread(info->name, 1, info->name_bytes, ar->file) != info->name_bytes) {
free(info);
return ERR(result, FILE_READ_FAILED);
}
if (fread(&info->start, sizeof (info->start), 1, ar->file) != 1) {
free(info);
return ERR(result, FILE_READ_FAILED);
}
if (fread(&info->bytes, sizeof (info->bytes), 1, ar->file) != 1) {
free(info);
return ERR(result, FILE_READ_FAILED);
}
return OK(result, info);
}
static size_result_t archive_write_info (archive_t* ar, archive_item_info_t* info)
{
assert(ar->opened == true);
assert(ar->mode == WRITE);
assert(ar->locked == false);
size_t bytes_written = 0;
size_result_t result = {0};
if (fwrite(&info->name_bytes, sizeof (info->name_bytes), 1, ar->file) != 1) {
return ERR(result, FILE_WRITE_FAILED);
}
bytes_written += sizeof (info->name_bytes);
if (fwrite(info->name, 1, info->name_bytes, ar->file) != info->name_bytes) {
return ERR(result, FILE_WRITE_FAILED);
}
bytes_written += info->name_bytes;
if (fwrite(&info->start, sizeof (info->start), 1, ar->file) != 1) {
return ERR(result, FILE_WRITE_FAILED);
}
bytes_written += sizeof (info->start);
if (fwrite(&info->bytes, sizeof (info->bytes), 1, ar->file) != 1) {
return ERR(result, FILE_WRITE_FAILED);
}
bytes_written += sizeof (info->bytes);
return OK(result, bytes_written);
}
// parse_index will seek to the end of the file, read the total index size, then read the index.
static empty_result_t archive_parse_index (archive_t* ar)
{
// The conditions covered by the assertions are usage error
assert(ar->opened == true);
assert(ar->mode == READ);
assert(ar->locked == false);
assert(ar->indexed == false);
ar->item_count = 0;
empty_result_t result = {0};
if (fseek(ar->file, -1 * sizeof (ar->index_bytes), SEEK_END) != 0) {
return ERR(result, FILE_SEEK_FAILED);
}
if (fread(&ar->index_bytes, sizeof (ar->index_bytes), 1, ar->file) != 1) {
return ERR(result, FILE_READ_FAILED);
}
if (fseek(ar->file, -1 * (ar->index_bytes + sizeof (ar->index_bytes)), SEEK_END) != 0) {
return ERR(result, FILE_SEEK_FAILED);
}
size_t bytes_remaining = ar->index_bytes;
while (bytes_remaining > 0) {
info_ref_result_t ir = archive_read_info(ar, bytes_remaining);
if (IS_ERR(ir)) {
return ERR(result, UNWRAP_ERR(ir));
}
archive_item_info_t* info = UNWRAP(ir);
bytes_remaining -= sizeof (info->start) + sizeof (info->bytes) + sizeof (info->name_bytes) + info->name_bytes;
size_t new_item_count = ar->item_count + 1;
archive_item_info_t** tmp = realloc(ar->items, sizeof (*tmp) * new_item_count);
if (tmp == NULL) {
free(info);
return ERR(result, FATAL_OUT_OF_MEMORY);
}
ar->items = tmp;
ar->items[ar->item_count] = info;
ar->item_count = new_item_count;
}
ar->indexed = true;
return OK_EMPTY(result);
}
static empty_result_t archive_write_index (archive_t* ar)
{
assert(ar->opened == true);
assert(ar->mode == WRITE);
assert(ar->locked == false);
empty_result_t result = {0};
size_t bytes_written = 0;
if (fseek(ar->file, 0, SEEK_END) != 0) {
return ERR(result, FILE_SEEK_FAILED);
}
for (size_t i = 0; i < ar->item_count; i++) {
size_result_t sr = archive_write_info(ar, ar->items[i]);
if (IS_ERR(sr)) {
return ERR(result, UNWRAP_ERR(sr));
}
bytes_written += UNWRAP(sr);
}
ar->index_bytes = bytes_written;
if (fwrite(&ar->index_bytes, sizeof (ar->index_bytes), 1, ar->file) != 1) {
return ERR(result, FILE_WRITE_FAILED);
}
}
static size_result_t archive_find_item (archive_t* ar, const byte_t* name, NAME_SIZE_TYPE name_bytes)
{
assert(ar->opened == true);
// assert(ar->indexed == true);
assert(ar->locked == false);
// assert(ar->mode == READ);