Files
pcp/libpcp/vault.c

543 lines
13 KiB
C
Raw Normal View History

2013-11-04 17:43:22 +01:00
/*
This file is part of Pretty Curved Privacy (pcp1).
Copyright (C) 2013 T.Linden.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tlinden AT cpan DOT org>.
*/
2013-10-28 22:50:05 +01:00
#include "vault.h"
#include "keyhash.h"
#include "defines.h"
2013-10-28 22:50:05 +01:00
vault_t *pcpvault_init(PCPCTX *ptx, char *filename) {
vault_t *vault = pcpvault_new(ptx, filename, 0);
2013-10-28 22:50:05 +01:00
if(vault != NULL) {
if(vault->isnew == 1) {
if(pcpvault_create(ptx, vault) != 0) {
pcpvault_close(ptx, vault);
2013-10-28 22:50:05 +01:00
return NULL;
}
}
else {
if(pcpvault_fetchall(ptx, vault) != 0) {
errno = 0; /* weird, something sets it to ENOENT and it's not me */
pcpvault_close(ptx, vault);
2013-10-28 22:50:05 +01:00
return NULL;
}
}
}
return vault;
}
vault_t *pcpvault_new(PCPCTX *ptx, char *filename, int is_tmp) {
2013-10-28 22:50:05 +01:00
vault_t *vault = ucmalloc(sizeof(vault_t));
FILE *fd;
struct stat stat_buf;
vault->filename = ucmalloc(1024);
2013-10-28 22:50:05 +01:00
if(is_tmp) {
uint32_t a,b;
while (1) {
a = arc4random();
b = arc4random();
snprintf(vault->filename, 1024, "%s-%08x%08x", filename, a, b);
if (stat (vault->filename, &stat_buf) != 0)
2013-10-28 22:50:05 +01:00
break;
}
unlink(vault->filename);
2013-10-28 22:50:05 +01:00
vault->size = 0;
vault->modified = 0;
vault->mode = 0;
}
else {
strncpy(vault->filename, filename, 1024);
if (stat (vault->filename, &stat_buf) == 0) {
2013-10-28 22:50:05 +01:00
vault->size = stat_buf.st_size;
vault->modified = stat_buf.st_mtime;
vault->mode = stat_buf.st_mode;
}
else {
vault->size = 0;
vault->modified = 0;
vault->mode = 0;
}
}
if(vault->size == 0) {
vault->isnew = 1;
mode_t old_mask = umask (S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH);
if((fd = fopen(vault->filename, "wb+")) == NULL) {
fatal(ptx, "Could not create vault file %s\n", vault->filename);
2013-10-28 22:50:05 +01:00
umask (old_mask);
goto errn;
}
umask (old_mask);
}
else {
if((fd = fopen(vault->filename, "rb+")) == NULL) {
fatal(ptx, "Could not open vault file %s\n", vault->filename);
2013-10-28 22:50:05 +01:00
goto errn;
}
}
vault->fd = fd;
vault->unsafed = 0;
return vault;
errn:
2014-08-07 00:02:43 +02:00
pcpvault_free(vault);
2013-10-28 22:50:05 +01:00
return NULL;
}
int pcpvault_create(PCPCTX *ptx, vault_t *vault) {
2013-10-28 22:50:05 +01:00
vault_header_t *header = ucmalloc(sizeof(vault_header_t));
header->fileid = PCP_VAULT_ID;
header->version = PCP_VAULT_VERSION;
vault->version = header->version;
memcpy(vault->checksum, header->checksum, LSHA);
2013-10-28 22:50:05 +01:00
vh2be(header);
fseek(vault->fd, 0, SEEK_SET);
fwrite(header, sizeof(vault_header_t), 1, vault->fd);
2014-08-07 00:02:43 +02:00
free(header);
2013-10-28 22:50:05 +01:00
if(ferror(vault->fd) != 0) {
fatal(ptx, "Failed to write fileheader to vault %s!\n", vault->filename);
2013-10-28 22:50:05 +01:00
return 1;
}
vault->unsafed = 0;
return 0;
}
int pcpvault_additem(PCPCTX *ptx, vault_t *vault, void *item, size_t itemsize, uint8_t type) {
2013-10-28 22:50:05 +01:00
vault_item_header_t *header = ucmalloc(sizeof(vault_item_header_t));
header->type = type;
header->size = itemsize;
crypto_hash_sha256((byte*)header->checksum, item, itemsize);
2013-10-28 22:50:05 +01:00
ih2be(header);
fwrite(header, sizeof(vault_item_header_t), 1, vault->fd);
2014-08-07 00:02:43 +02:00
free(header);
2013-11-18 21:48:24 +01:00
fwrite(item, itemsize, 1, vault->fd);
2013-10-28 22:50:05 +01:00
if(ferror(vault->fd) != 0) {
fatal(ptx, "Failed to add an item to vault %s!\n", vault->filename);
2013-10-28 22:50:05 +01:00
return 1;
}
vault->unsafed = 0;
return 0;
}
int pcpvault_addkey(PCPCTX *ptx, vault_t *vault, void *item, uint8_t type) {
vault_t *tmp = pcpvault_new(ptx, vault->filename, 1);
size_t itemsize;
void *saveitem = NULL;
2014-08-07 00:02:43 +02:00
Buffer *blob = NULL;
2013-11-18 21:48:24 +01:00
if(type == PCP_KEY_TYPE_PUBLIC) {
itemsize = PCP_RAW_PUBKEYSIZE;
2013-11-18 21:48:24 +01:00
saveitem = ucmalloc(sizeof(pcp_pubkey_t));
memcpy(saveitem, item, sizeof(pcp_pubkey_t));
pubkey2be((pcp_pubkey_t *)item);
2014-08-07 00:02:43 +02:00
blob = buffer_new(PCP_RAW_KEYSIZE, "bs");
2014-08-06 01:23:32 +02:00
pcp_pubkeyblob(blob, (pcp_pubkey_t *)item);
}
2014-08-07 21:33:52 +02:00
else if(type == PCP_KEYSIG_NATIVE || type == PCP_KEYSIG_PBP) {
2014-08-07 00:02:43 +02:00
saveitem = ucmalloc(sizeof(pcp_keysig_t));
2014-08-07 21:33:52 +02:00
pcp_keysig_t *ksin = (pcp_keysig_t *)item;
pcp_keysig_t *ksout = (pcp_keysig_t *)saveitem;
memcpy(ksout, ksin, sizeof(pcp_keysig_t));
ksout->blob = ucmalloc(ksin->size);
memcpy(ksout->blob, ksin->blob, ksin->size);
2014-08-07 00:02:43 +02:00
blob = pcp_keysig2blob(item);
itemsize = buffer_size(blob);
}
else {
itemsize = PCP_RAW_KEYSIZE;
2013-11-18 21:48:24 +01:00
saveitem = ucmalloc(sizeof(pcp_key_t));
memcpy(saveitem, item, sizeof(pcp_key_t));
key2be((pcp_key_t *)item);
blob = pcp_keyblob(item, type);
2014-08-06 01:23:32 +02:00
pcp_seckeyblob(blob, (pcp_key_t *)item);
}
if(tmp != NULL) {
if(pcpvault_copy(ptx, vault, tmp) != 0)
goto errak1;
if(pcpvault_additem(ptx, tmp, buffer_get(blob), itemsize, type) != 0)
goto errak1;
2013-11-18 21:48:24 +01:00
pcphash_add(ptx, saveitem, type);
pcpvault_update_checksum(ptx, tmp);
2013-11-18 21:48:24 +01:00
if(pcpvault_copy(ptx, tmp, vault) == 0) {
pcpvault_unlink(tmp);
}
else {
fprintf(stderr, "Keeping tmp vault %s\n", tmp->filename);
goto errak1;
}
buffer_free(blob);
2014-08-07 00:02:43 +02:00
pcpvault_free(tmp);
return 0;
}
errak1:
buffer_free(blob);
if(tmp != NULL) {
free(tmp);
}
return 1;
2013-10-28 22:50:05 +01:00
}
int pcpvault_writeall(PCPCTX *ptx, vault_t *vault) {
vault_t *tmp = pcpvault_new(ptx, vault->filename, 1);
2013-10-28 22:50:05 +01:00
if(tmp != NULL) {
if(pcpvault_create(ptx, tmp) == 0) {
pcp_key_t *k = NULL;
Buffer *blob = buffer_new(PCP_RAW_PUBKEYSIZE, "bs");
pcphash_iterate(ptx, k) {
pcp_seckeyblob(blob, k);
if(pcpvault_additem(ptx, tmp, buffer_get(blob), PCP_RAW_KEYSIZE, PCP_KEY_TYPE_SECRET) != 0) {
buffer_free(blob);
2013-10-28 22:50:05 +01:00
goto errwa;
}
buffer_clear(blob);
2013-10-28 22:50:05 +01:00
}
pcp_pubkey_t *p = NULL;
pcphash_iteratepub(ptx, p) {
pcp_pubkeyblob(blob, p);
if(pcpvault_additem(ptx, tmp, buffer_get(blob), PCP_RAW_PUBKEYSIZE, PCP_KEY_TYPE_PUBLIC) != 0) {
buffer_free(blob);
2013-10-28 22:50:05 +01:00
goto errwa;
}
buffer_clear(blob);
2013-10-28 22:50:05 +01:00
}
pcpvault_update_checksum(ptx, tmp);
if(pcpvault_copy(ptx, tmp, vault) == 0) {
pcpvault_unlink(tmp);
}
2014-08-08 18:40:53 +02:00
pcpvault_free(tmp);
buffer_free(blob);
return 0;
2013-10-28 22:50:05 +01:00
}
}
return 1;
2013-10-28 22:50:05 +01:00
errwa:
if(tmp != NULL) {
pcpvault_unlink(tmp);
free(tmp);
}
return 1;
}
void pcpvault_update_checksum(PCPCTX *ptx, vault_t *vault) {
byte *checksum = pcpvault_create_checksum(ptx);
2013-10-28 22:50:05 +01:00
vault_header_t *header = ucmalloc(sizeof(vault_header_t));
header->fileid = PCP_VAULT_ID;
header->version = PCP_VAULT_VERSION;
memcpy(header->checksum, checksum, LSHA);
memcpy(vault->checksum, checksum, LSHA);
ucfree(checksum, LSHA);
2013-10-28 22:50:05 +01:00
vh2be(header);
fseek(vault->fd, 0, SEEK_SET);
fwrite(header, sizeof(vault_header_t), 1, vault->fd);
2014-08-07 00:02:43 +02:00
free(header);
2013-10-28 22:50:05 +01:00
fseek(vault->fd, 0, SEEK_END);
}
byte *pcpvault_create_checksum(PCPCTX *ptx) {
pcp_key_t *k = NULL;
2014-08-06 01:23:32 +02:00
Buffer *blob = buffer_new(PCP_RAW_KEYSIZE, "blob");;
size_t datapos = 0;
int numskeys = pcphash_count(ptx);
int numpkeys = pcphash_countpub(ptx);
2013-10-28 22:50:05 +01:00
size_t datasize = ((PCP_RAW_KEYSIZE) * numskeys) +
((PCP_RAW_PUBKEYSIZE) * numpkeys);
byte *data = ucmalloc(datasize);
byte *checksum = ucmalloc(LSHA);
2013-10-28 22:50:05 +01:00
pcphash_iterate(ptx, k) {
2013-10-28 22:50:05 +01:00
key2be(k);
2014-08-06 01:23:32 +02:00
pcp_seckeyblob(blob, (pcp_key_t *)k);
memcpy(&data[datapos], buffer_get(blob), PCP_RAW_KEYSIZE);
buffer_clear(blob);
2013-10-28 22:50:05 +01:00
key2native(k);
datapos += PCP_RAW_KEYSIZE;
2013-10-28 22:50:05 +01:00
}
pcp_pubkey_t *p = NULL;
pcphash_iteratepub(ptx, p) {
/* pcp_dumppubkey(p); */
2013-10-28 22:50:05 +01:00
pubkey2be(p);
2014-08-06 01:23:32 +02:00
pcp_pubkeyblob(blob, (pcp_pubkey_t *)p);
memcpy(&data[datapos], buffer_get(blob), PCP_RAW_PUBKEYSIZE);
buffer_clear(blob);
2013-10-28 22:50:05 +01:00
pubkey2native(p);
datapos += PCP_RAW_PUBKEYSIZE;
2013-10-28 22:50:05 +01:00
}
buffer_free(blob);
2013-10-28 22:50:05 +01:00
crypto_hash_sha256(checksum, data, datasize);
2013-10-29 23:08:43 +01:00
memset(data, 0, datasize);
2013-10-28 22:50:05 +01:00
free(data);
return checksum;
}
int pcpvault_copy(PCPCTX *ptx, vault_t *tmp, vault_t *vault) {
/* fetch tmp content */
2013-10-28 22:50:05 +01:00
fseek(tmp->fd, 0, SEEK_END);
int tmpsize = ftell(tmp->fd);
fseek(tmp->fd, 0, SEEK_SET);
byte *in = ucmalloc(tmpsize);
2014-03-10 17:01:00 +01:00
tmpsize = fread(in, 1, tmpsize, tmp->fd);
2013-10-28 22:50:05 +01:00
/* and put it into the new file */
2013-10-28 22:50:05 +01:00
vault->fd = freopen(vault->filename, "wb+", vault->fd);
if(fwrite(in, tmpsize, 1, vault->fd) != 1) {
fatal(ptx, "Failed to copy %s to %s (write) [keeping %s]\n",
tmp->filename, vault->filename, tmp->filename);
2014-08-07 00:02:43 +02:00
ucfree(in, tmpsize);
return 1;
}
2014-08-07 00:02:43 +02:00
ucfree(in, tmpsize);
if(fflush(vault->fd) != 0) {
fatal(ptx, "Failed to copy %s to %s (flush) [keeping %s]\n",
tmp->filename, vault->filename, tmp->filename);
return 1;
}
return 0;
2013-10-28 22:50:05 +01:00
}
void pcpvault_unlink(vault_t *tmp) {
int i, tmpsize;
byte *r;
2013-10-28 22:50:05 +01:00
fseek(tmp->fd, 0, SEEK_END);
tmpsize = ftell(tmp->fd);
r = ucmalloc(tmpsize);
for (i=0; i<16; ++i) {
fseek(tmp->fd, 0, SEEK_SET);
arc4random_buf(r, tmpsize);
fwrite(r, tmpsize, 1, tmp->fd);
}
fclose(tmp->fd);
unlink(tmp->filename);
free(r);
}
int pcpvault_close(PCPCTX *ptx, vault_t *vault) {
2013-10-28 22:50:05 +01:00
if(vault != NULL) {
if(vault->fd) {
if(vault->unsafed == 1) {
pcpvault_writeall(ptx, vault);
2013-10-28 22:50:05 +01:00
}
fclose(vault->fd);
}
2014-08-07 00:02:43 +02:00
pcpvault_free(vault);
2013-10-28 22:50:05 +01:00
vault = NULL;
}
return 0;
}
2014-08-07 00:02:43 +02:00
void pcpvault_free(vault_t *vault) {
if(vault != NULL) {
free(vault->filename);
free(vault);
}
}
2013-10-28 22:50:05 +01:00
vault_header_t * vh2be(vault_header_t *h) {
#ifdef __CPU_IS_BIG_ENDIAN
2013-11-18 21:48:24 +01:00
return h;
#else
2013-10-28 22:50:05 +01:00
h->version = htobe32(h->version);
return h;
2013-11-18 21:48:24 +01:00
#endif
2013-10-28 22:50:05 +01:00
}
vault_header_t * vh2native(vault_header_t *h) {
#ifdef __CPU_IS_BIG_ENDIAN
2013-11-18 21:48:24 +01:00
return h;
#else
2013-10-28 22:50:05 +01:00
h->version = be32toh(h->version);
return h;
2013-11-18 21:48:24 +01:00
#endif
2013-10-28 22:50:05 +01:00
}
vault_item_header_t * ih2be(vault_item_header_t *h) {
#ifdef __CPU_IS_BIG_ENDIAN
2013-11-18 21:48:24 +01:00
return h;
#else
2013-10-28 22:50:05 +01:00
h->version = htobe32(h->version);
h->size = htobe32(h->size);
return h;
2013-11-18 21:48:24 +01:00
#endif
2013-10-28 22:50:05 +01:00
}
vault_item_header_t * ih2native(vault_item_header_t *h) {
#ifdef __CPU_IS_BIG_ENDIAN
2013-11-18 21:48:24 +01:00
return h;
#else
2013-10-28 22:50:05 +01:00
h->version = be32toh(h->version);
h->size = be32toh(h->size);
return h;
2013-11-18 21:48:24 +01:00
#endif
2013-10-28 22:50:05 +01:00
}
int pcpvault_fetchall(PCPCTX *ptx, vault_t *vault) {
2014-03-10 17:01:00 +01:00
size_t got = 0;
2013-10-28 22:50:05 +01:00
fseek(vault->fd, 0, SEEK_SET);
vault_header_t *header = ucmalloc(sizeof(vault_header_t));
vault_item_header_t *item = ucmalloc(sizeof(vault_item_header_t));
2014-03-10 17:01:00 +01:00
got = fread(header, 1, sizeof(vault_header_t), vault->fd);
if(got < sizeof(vault_header_t)) {
fatal(ptx, "empty or invalid vault header size (got %ld, expected %ld)\n",
got, sizeof(vault_header_t));
2014-03-10 17:01:00 +01:00
goto err;
}
2013-10-28 22:50:05 +01:00
vh2native(header);
if(header->fileid == PCP_VAULT_ID && header->version == PCP_VAULT_VERSION) {
/* loop over the file and slurp everything in */
2014-03-10 17:01:00 +01:00
size_t readpos = 0;
2013-10-28 22:50:05 +01:00
pcp_key_t *key;
pcp_pubkey_t *pubkey;
int bytesleft = 0;
int ksize = PCP_RAW_KEYSIGSIZE; /* smallest possbile item */
2013-10-28 22:50:05 +01:00
vault->version = header->version;
memcpy(vault->checksum, header->checksum, LSHA);
2013-10-28 22:50:05 +01:00
for(;;) {
readpos = ftell(vault->fd);
if(vault->size - readpos >= sizeof(vault_item_header_t)) {
/* an item header follows */
2014-03-10 17:01:00 +01:00
got = fread(item, sizeof(vault_item_header_t), 1, vault->fd);
2013-10-28 22:50:05 +01:00
ih2native(item);
if(item->size > 0) {
/* item is valid */
2013-10-28 22:50:05 +01:00
readpos = ftell(vault->fd);
bytesleft = vault->size - readpos;
if(bytesleft >= ksize) {
/* a key follows */
2013-10-28 22:50:05 +01:00
if(item->type == PCP_KEY_TYPE_MAINSECRET ||
item->type == PCP_KEY_TYPE_SECRET) {
/* read a secret key */
2013-10-28 22:50:05 +01:00
key = ucmalloc(sizeof(pcp_key_t));
2014-03-10 17:01:00 +01:00
got = fread(key, PCP_RAW_KEYSIZE, 1, vault->fd);
2013-10-28 22:50:05 +01:00
key2native(key);
pcphash_add(ptx, (void *)key, item->type);
2013-10-28 22:50:05 +01:00
}
else if(item->type == PCP_KEY_TYPE_PUBLIC) {
/* read a public key */
2013-10-28 22:50:05 +01:00
pubkey = ucmalloc(sizeof(pcp_pubkey_t));
2014-03-10 17:01:00 +01:00
got = fread(pubkey, PCP_RAW_PUBKEYSIZE, 1, vault->fd);
2013-10-28 22:50:05 +01:00
pubkey2native(pubkey);
pcphash_add(ptx, (void *)pubkey, item->type);
2013-10-28 22:50:05 +01:00
}
else if(item->type == PCP_KEYSIG_NATIVE || item->type == PCP_KEYSIG_PBP) {
Buffer *rawks = buffer_new(256, "keysig");
buffer_fd_read(rawks, vault->fd, item->size);
pcp_keysig_t *s = pcp_keysig_new(rawks);
pcphash_add(ptx, (void *)s, item->type);
buffer_free(rawks);
}
2013-10-28 22:50:05 +01:00
else {
fatal(ptx, "Failed to read vault - invalid key type: %02X! at %d\n",
item->type, readpos);
2013-10-28 22:50:05 +01:00
goto err;
}
}
else {
fatal(ptx, "Failed to read vault - that's no pcp key at %d (size %ld)!\n",
readpos, bytesleft);
2013-10-28 22:50:05 +01:00
goto err;
}
}
else {
fatal(ptx, "Failed to read vault - invalid key item header size at %d!\n",
2013-10-28 22:50:05 +01:00
readpos);
goto err;
}
}
else {
/* no more items */
2013-10-28 22:50:05 +01:00
break;
}
}
}
else {
fatal(ptx, "Unexpected vault file format!\n");
2013-10-28 22:50:05 +01:00
goto err;
}
byte *checksum = NULL;
checksum = pcpvault_create_checksum(ptx);
if(pcphash_count(ptx) + pcphash_countpub(ptx) > 0) {
/* only validate the checksum if there are keys */
if(memcmp(checksum, vault->checksum, LSHA) != 0) {
fatal(ptx, "Error: the checksum of the key vault doesn't match its contents!\n");
2013-10-28 22:50:05 +01:00
goto err;
}
}
free(checksum);
free(item);
free(header);
return 0;
err:
free(item);
free(header);
/* pcphash_clean(); */
2013-10-28 22:50:05 +01:00
return -1;
}