diff -r 83263002816f -r 09ac07345656 application/pwd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/pwd.c Mon Jan 29 10:41:00 2024 +0100 @@ -0,0 +1,472 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "pwd.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#endif + +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; +} + +static int readval(CxBuffer *in, char **val, int allowzero) { + // value = length string + // length = uint32 + // string = bytes + + *val = NULL; + + // get length + uint32_t length = 0; + if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) { + return 0; + } + length = ntohl(length); // convert from BE to host byte order + if(length == 0) { + if(allowzero) { + return 1; + } else { + return 0; + } + } + if(length > PWDSTORE_MAX_LEN) { + return 0; + } + + // get value + 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) { + // read type of index element + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + locations->simple_destructor = free; + + // get id (required) + int ret = 0; + if(readval(in, &id, FALSE)) { + ret = 1; + // get locations + char *location = NULL; + while((ret = readval(in, &location, TRUE)) == 1) { + if(!location) { + break; + } + cxListAdd(locations, location); + } + } + + if(ret) { + pwdstore_put_index(p, id, 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) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + char *location = 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(location) free(location); + if(user) free(user); + if(password) free(password); + + return ret; +} + +static int remove_list_entries(PwdStore *s, const char *id) { + int ret = 0; + + CxList *loc_entry = NULL; + CxList *noloc_entry = NULL; + + CxMutIterator i = cxListMutIterator(s->locations); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + // TODO: break loop + } + } + i = cxListMutIterator(s->noloc); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + // TODO: break loop + } + } + + return ret; +} + +void pwdstore_remove_entry(PwdStore *s, const char *id) { + while(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) { + 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; + + // set the position to the last 4 bytes of the header + // for reading index length + s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); + + // read indexlen and convert to host byte order + if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) { + return 1; + } + uint32_t indexlen = ntohl(netindexlen); + + // integer overflow check + if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) { + return 1; + } + if(s->content->size < PWDS_HEADER_SIZE + indexlen) { + return 1; + } + // encrypted content starts after the index content + s->encoffset = PWDS_HEADER_SIZE + indexlen; + + // the index starts after the header + CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0); + index->size = indexlen; + + // read index + while(read_indexentry(s, index)) {} + + // free index buffer structure (not the content) + cxBufferFree(index); + + return 0; +} + +int pwdstore_decrypt(PwdStore *p) { + if(!p->key) { + return 1; + } + if(p->isdecrypted) { + return 0; + } + + // decrypt contet + 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); + + 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) { + p->ids->simple_destructor = (cx_destructor_func)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) { + 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); // type + + cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out); + cxBufferWrite(e->id, 1, idlen, out); + + 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); + + // create index + 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); + + // content buffer + cxBufferPut(content, 0); // type + + 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; + + // add index after header + uint32_t netindexlen = htonl((uint32_t)index->size); + cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content); + cxBufferWrite(index->space, 1, index->size, p->content); + + // add encrypted buffer + 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; +}