#include "pwdstore.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cx/utils.h>
#include <cx/hash_map.h>
#ifdef _WIN32
#include <winsock.h>
#pragma comment(lib,
"Ws2_32.lib")
#else
#include <netinet/in.h>
#endif
static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput;
static void *pw_input_data =
"Master password: ";
char * pwdstore_default_pwinput(
char *prompt) {
return util_password_input(prompt);
}
void pwdstore_set_pwinput_func(pwdstore_pwinput_func func,
void *userdata) {
pw_input = func;
pw_input_data = userdata;
}
PwdStore* pwdstore_open(
const char *file) {
FILE *in = fopen(file,
"r");
if(!in) {
return NULL;
}
CxBuffer *buf = cxBufferCreate(
NULL,
2048, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
fclose(in);
if(buf->size <
PWDS_HEADER_SIZE || buf->space[
0] !=
PWDS_MAGIC_CHAR) {
cxBufferFree(buf);
return NULL;
}
PwdStore *p = malloc(
sizeof(PwdStore));
p->ids = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
p->locations = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
p->noloc = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
p->index = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
p->content = buf;
p->key =
NULL;
p->unlock_cmd =
NULL;
p->lock_cmd =
NULL;
p->encoffset =
PWDS_HEADER_SIZE;
p->isdecrypted =
0;
if(pwdstore_getindex(p)) {
pwdstore_free(p);
return NULL;
}
return p;
}
PwdStore* pwdstore_new(
void) {
PwdStore *p = calloc(
1,
sizeof(PwdStore));
p->ids = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
p->locations = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
p->noloc = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
p->index = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
p->content = cxBufferCreate(
NULL,
PWDS_HEADER_SIZE, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
PWDS_MAGIC(p) =
PWDS_MAGIC_CHAR;
PWDS_VERSION(p) =
1;
PWDS_ENC(p) =
DAV_KEY_AES256;
PWDS_PWFUNC(p) =
DAV_PWFUNC_PBKDF2_SHA256;
dav_rand_bytes((
unsigned char*)p->content->space+
4,
16);
p->isdecrypted =
1;
p->encoffset =
PWDS_HEADER_SIZE;
return p;
}
PwdStore* pwdstore_clone(PwdStore *p) {
CxBuffer *newbuffer = calloc(
1,
sizeof(CxBuffer));
*newbuffer = *p->content;
newbuffer->space = malloc(p->content->capacity);
memcpy(newbuffer->space, p->content->space, p->content->capacity);
DavKey *key =
NULL;
if(p->key) {
key = malloc(
sizeof(DavKey));
key->data = malloc(p->key->length);
memcpy(key->data, p->key->data, p->key->length);
key->length = p->key->length;
key->type = p->key->type;
key->name =
NULL;
}
PwdStore *newp = calloc(
1,
sizeof(PwdStore));
newp->ids = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
newp->locations = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
newp->noloc = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
newp->index = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
newp->content = newbuffer;
newp->key = key;
newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) :
NULL;
newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) :
NULL;
newp->encoffset = p->encoffset;
newp->isdecrypted = p->isdecrypted;
CxIterator i = cxMapIterator(p->ids);
cx_foreach(CxMapEntry *, e, i) {
PwdEntry *entry = e->value;
pwdstore_put(newp, entry->id, entry->user, entry->password);
}
i = cxMapIterator(p->index);
cx_foreach(CxMapEntry *, e, i) {
PwdIndexEntry *entry = e->value;
CxList *locations =
NULL;
if(entry->locations) {
locations = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxIterator li = cxListIterator(entry->locations);
cx_foreach(
char *, location, li) {
cxListAdd(locations, strdup(location));
}
}
pwdstore_put_index(newp, strdup(entry->id), locations);
}
return newp;
}
static int readval(CxBuffer *in,
char **val,
int allowzero) {
*val =
NULL;
uint32_t length =
0;
if(cxBufferRead(&length,
1,
sizeof(
uint32_t), in) !=
sizeof(
uint32_t)) {
return 0;
}
length = ntohl(length);
if(length ==
0) {
if(allowzero) {
return 1;
}
else {
return 0;
}
}
if(length >
PWDSTORE_MAX_LEN) {
return 0;
}
char *value = malloc(length +
1);
value[length] =
0;
if(cxBufferRead(value,
1, length, in) != length) {
free(value);
return 0;
}
*val = value;
return 1;
}
static int read_indexentry(PwdStore *p, CxBuffer *in) {
int type = cxBufferGet(in);
if(type ==
EOF || type !=
0) {
return 0;
}
char *id =
NULL;
CxList *locations = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(locations, free);
int ret =
0;
if(readval(in, &id,
FALSE)) {
char *location =
NULL;
while((ret = readval(in, &location,
TRUE)) ==
1) {
if(!location) {
break;
}
cxListAdd(locations, location);
}
}
if(ret) {
pwdstore_put_index(p, id, locations);
if(cxListSize(locations) ==
0) {
cxListDestroy(locations);
}
}
else {
if(id) free(id);
cxListDestroy(locations);
}
return ret;
}
static int read_pwdentry(PwdStore *p, CxBuffer *in) {
int type = cxBufferGet(in);
if(type ==
EOF || type !=
0) {
return 0;
}
char *id =
NULL;
char *user =
NULL;
char *password =
NULL;
int ret =
0;
if(readval(in, &id,
FALSE)) {
if(readval(in, &user,
FALSE)) {
if(readval(in, &password,
FALSE)) {
pwdstore_put(p, id, user, password);
ret =
1;
}
}
}
if(id) free(id);
if(user) free(user);
if(password) free(password);
return ret;
}
static void remove_list_entries(PwdStore *s,
const char *id) {
CxIterator i = cxListMutIterator(s->locations);
cx_foreach(PwdIndexEntry*, ie, i) {
if(!strcmp(ie->id, id)) {
cxIteratorFlagRemoval(i);
cxIteratorNext(i);
break;
}
}
i = cxListMutIterator(s->noloc);
cx_foreach(PwdIndexEntry*, ie, i) {
if(!strcmp(ie->id, id)) {
cxIteratorFlagRemoval(i);
cxIteratorNext(i);
break;
}
}
}
void pwdstore_remove_entry(PwdStore *s,
const char *id) {
remove_list_entries(s, id);
CxHashKey key = cx_hash_key_str(id);
PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key);
PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
if(i) {
if(i->locations) {
cxListDestroy(i->locations);
}
free(i->id);
free(i);
}
if(e) {
free(e->id);
free(e->user);
free(e->password);
free(e);
}
}
int pwdstore_getindex(PwdStore *s) {
uint32_t netindexlen;
s->content->pos =
PWDS_HEADER_SIZE -
sizeof(
uint32_t);
if(cxBufferRead(&netindexlen,
1,
sizeof(
uint32_t), s->content) !=
sizeof(
uint32_t)) {
return 1;
}
uint32_t indexlen = ntohl(netindexlen);
if(
UINT32_MAX -
PWDS_HEADER_SIZE < indexlen) {
return 1;
}
if(s->content->size <
PWDS_HEADER_SIZE + indexlen) {
return 1;
}
s->encoffset =
PWDS_HEADER_SIZE + indexlen;
CxBuffer *index = cxBufferCreate(s->content->space+
PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator,
0);
index->size = indexlen;
while(read_indexentry(s, index)) {}
cxBufferFree(index);
return 0;
}
int pwdstore_decrypt(PwdStore *p) {
if(!p->key) {
return 1;
}
if(p->isdecrypted) {
return 0;
}
size_t encsz = p->content->size - p->encoffset;
CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator,
0);
enc->size = encsz;
enc->size = p->content->size - p->encoffset;
CxBuffer *content = aes_decrypt_buffer(enc, p->key);
cxBufferFree(enc);
if(!content) {
return 1;
}
while(read_pwdentry(p, content)) {}
cxBufferFree(content);
p->isdecrypted =
1;
return 0;
}
int pwdstore_setpassword(PwdStore *p,
const char *password) {
DavKey *key = dav_pw2key(
password,
(
unsigned char*)(p->content->space +
4),
16,
PWDS_PWFUNC(p),
PWDS_ENC(p));
if(!key) {
return 1;
}
p->key = key;
return 0;
}
void pwdstore_encsettings(PwdStore *p,
uint8_t enc,
uint8_t pwfunc) {
PWDS_ENC(p) = enc;
PWDS_PWFUNC(p) = pwfunc;
}
void pwdstore_free_entry(PwdEntry *e) {
if(e->id) free(e->id);
if(e->user) free(e->user);
if(e->password) free(e->password);
free(e);
}
void pwdstore_free(PwdStore* p) {
cxDefineDestructor(p->ids, pwdstore_free_entry);
cxMapDestroy(p->ids);
cxListDestroy(p->locations);
if(p->content) {
cxBufferFree(p->content);
}
free(p);
}
int pwdstore_has_id(PwdStore *s,
const char *id) {
return cxMapGet(s->index, cx_hash_key_str(id)) ?
1 :
0;
}
PwdEntry* pwdstore_get(PwdStore *p,
const char *id) {
PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id));
if(e && e->user && e->password) {
return e;
}
else {
return NULL;
}
}
void pwdstore_put(PwdStore *p,
const char *id,
const char *username,
const char *password) {
PwdEntry *entry = malloc(
sizeof(PwdEntry));
entry->id = strdup(id);
entry->user = strdup(username);
entry->password = strdup(password);
cxMapPut(p->ids, cx_hash_key_str(id), entry);
}
void pwdstore_put_index(PwdStore *p,
char *id, CxList *locations) {
PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id));
if(e) {
return;
}
PwdIndexEntry *newentry = malloc(
sizeof(PwdIndexEntry));
newentry->id = id;
if(locations && cxListSize(locations) >
0) {
newentry->locations = locations;
cxListAdd(p->locations, newentry);
}
else {
newentry->locations =
NULL;
cxListAdd(p->noloc, newentry);
}
cxMapPut(p->index, cx_hash_key_str(id), newentry);
}
void write_index_entry(CxBuffer *out, PwdIndexEntry *e) {
uint32_t idlen = strlen(e->id);
uint32_t netidlen = htonl(idlen);
cxBufferPut(out,
0);
cxBufferWrite(&netidlen,
1,
sizeof(
uint32_t), out);
cxBufferWrite(e->id,
1, idlen, out);
if(e->locations) {
CxIterator i = cxListIterator(e->locations);
cx_foreach(
char *, location, i) {
uint32_t locationlen = strlen(location);
uint32_t netlocationlen = htonl(locationlen);
cxBufferWrite(&netlocationlen,
1,
sizeof(
uint32_t), out);
cxBufferWrite(location,
1, locationlen, out);
}
}
uint32_t terminate =
0;
cxBufferWrite(&terminate,
1,
sizeof(
uint32_t), out);
}
int pwdstore_store(PwdStore *p,
const char *file) {
if(!p->key) {
return 1;
}
CxBuffer *index = cxBufferCreate(
NULL,
2048, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CxBuffer *content = cxBufferCreate(
NULL,
2048, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CxIterator i = cxListIterator(p->noloc);
cx_foreach(PwdIndexEntry*, e, i) {
write_index_entry(index, e);
}
i = cxListIterator(p->locations);
cx_foreach(PwdIndexEntry*, e, i) {
write_index_entry(index, e);
}
i = cxMapIteratorValues(p->ids);
cx_foreach(PwdEntry*, value, i) {
if(!value->id || !value->user || !value->password) {
continue;
}
uint32_t idlen = strlen(value->id);
uint32_t ulen = strlen(value->user);
uint32_t plen = strlen(value->password);
uint32_t netidlen = htonl(idlen);
uint32_t netulen = htonl(ulen);
uint32_t netplen = htonl(plen);
cxBufferPut(content,
0);
cxBufferWrite(&netidlen,
1,
sizeof(
uint32_t), content);
cxBufferWrite(value->id,
1, idlen, content);
cxBufferWrite(&netulen,
1,
sizeof(
uint32_t), content);
cxBufferWrite(value->user,
1, ulen, content);
cxBufferWrite(&netplen,
1,
sizeof(
uint32_t), content);
cxBufferWrite(value->password,
1, plen, content);
}
content->pos =
0;
CxBuffer *enc = aes_encrypt_buffer(content, p->key);
p->content->pos =
PWDS_HEADER_SIZE -
sizeof(
uint32_t);
p->content->size =
PWDS_HEADER_SIZE;
uint32_t netindexlen = htonl((
uint32_t)index->size);
cxBufferWrite(&netindexlen,
1,
sizeof(
uint32_t), p->content);
cxBufferWrite(index->space,
1, index->size, p->content);
cxBufferWrite(enc->space,
1, enc->size, p->content);
cxBufferFree(enc);
FILE *out = fopen(file,
"w");
if(!out) {
return 1;
}
fwrite(p->content->space,
1, p->content->size, out);
fclose(out);
return 0;
}
int pwdstore_decrypt_secrets(PwdStore *secrets) {
if(!pw_input) {
return 1;
}
char *ps_password =
NULL;
if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) >
0) {
CxBuffer *cmd_out = cxBufferCreate(
NULL,
128, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
if(!util_exec_command(secrets->unlock_cmd, cmd_out)) {
size_t len =
0;
for(
size_t i=
0;i<=cmd_out->size;i++) {
if(i == cmd_out->size || cmd_out->space[i] ==
'\n') {
len = i;
break;
}
}
if(len >
0) {
ps_password = malloc(len +
1);
memcpy(ps_password, cmd_out->space, len);
ps_password[len] =
0;
}
}
cxBufferFree(cmd_out);
}
if(!ps_password) {
ps_password = pw_input(pw_input_data);
if(!ps_password) {
return 1;
}
}
int err = pwdstore_setpassword(secrets, ps_password);
free(ps_password);
if(err) {
fprintf(stderr,
"Error: cannot create key from password\n");
return 1;
}
if(pwdstore_decrypt(secrets)) {
fprintf(stderr,
"Error: cannot decrypt secrets store\n");
return 1;
}
return 0;
}