Thu, 28 Nov 2024 17:18:43 +0100
fix property set/remove list size checks
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 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 "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) { // 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); cxDefineDestructor(locations, free); // get id (required) int ret = 0; if(readval(in, &id, FALSE)) { // 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); 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) { // only type 0 supported yet 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; // 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); 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); // type 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); // 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; } 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)) { // command successful, get first line from output without newline // and use that as password for the secretstore 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; }