kee

Offline IOU signer with QR as transport
git clone https://holbrook.no/src/kee
Info | Log | Files | Refs | README | LICENSE

commit 3dd7f20a73f39046f2129eddff4cbb91a6635450
parent 26217dcc14363d8118d157a6a27691094c9a1546
Author: lash <dev@holbrook.no>
Date:   Fri, 19 Apr 2024 14:34:59 +0100

WIP add signature verify in entry deserialize

Diffstat:
MMakefile | 4++--
Msrc/hex.c | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/hex.h | 1+
Asrc/ledger.c | 464+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ledger.h | 37+++++++++++++++++++++++++++++++++++++
Msrc/tests/Makefile | 9++++++---
Asrc/tests/ledger.c | 33+++++++++++++++++++++++++++++++++
7 files changed, 586 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile @@ -23,7 +23,7 @@ debug: gtk all G_DEBUG=all G_MESSAGES_DEBUG=all gdb ./src/gtk/a.out #test: gtk all test_src test_gtk -test: testdata test_src test_gtk +test: test_src test_gtk testdata_schema: asn1ate src/asn1/schema_entry.txt > testdata_asn1schema.py @@ -31,7 +31,7 @@ testdata_schema: test_src: all make -C src/tests test -test_gtk: gtk all +test_gtk: testdata gtk all make -C src/gtk/tests test testdata: testdata_schema diff --git a/src/hex.c b/src/hex.c @@ -1,8 +1,51 @@ +#include <string.h> #include <stddef.h> #include <stdio.h> #include "hex.h" +// cheekily stolen from https://nachtimwald.com/2017/09/24/hex-encode-and-decode-in-c/ +int hexchr2bin(const char hex, char *out) { + if (out == NULL) + return 0; + + if (hex >= '0' && hex <= '9') { + *out = hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + *out = hex - 'A' + 10; + } else if (hex >= 'a' && hex <= 'f') { + *out = hex - 'a' + 10; + } else { + return 0; + } + + return 1; +} + +size_t hex2bin(const char *hex, unsigned char *out) { + size_t len; + char b1; + char b2; + size_t i; + + if (hex == NULL || *hex == '\0' || out == NULL) + return 0; + + len = strlen(hex); + if (len % 2 != 0) + return 0; + len /= 2; + + memset(out, 'A', len); + for (i=0; i<len; i++) { + if (!hexchr2bin(hex[i*2], &b1) || !hexchr2bin(hex[i*2+1], &b2)) { + return 0; + } + //(*out)[i] = (b1 << 4) | b2; + *(out+i) = (b1 << 4) | b2; + } + return len; +} int bin_to_hex(const unsigned char *data, size_t l, unsigned char *zHex, size_t *z) { unsigned int i; diff --git a/src/hex.h b/src/hex.h @@ -16,6 +16,7 @@ extern "C" { * \return 1 if Output buffer is insufficient to write the result string, otherwise 0 (success). */ int bin_to_hex(const unsigned char *data, size_t l, unsigned char *zHex, size_t *z); +size_t hex2bin(const char *hex, unsigned char *out); #ifdef __cplusplus } diff --git a/src/ledger.c b/src/ledger.c @@ -0,0 +1,464 @@ +#include <stddef.h> +#include <libtasn1.h> +#include <gcrypt.h> + +//#include "cmime.h" + +#include "ledger.h" +#include "cadiz.h" +#include "err.h" +#include "debug.h" +#include "digest.h" +#include "strip.h" + + +extern const asn1_static_node schema_entry_asn1_tab[]; + +int kee_ledger_resolve(struct kee_ledger_t *ledger, struct Cadiz *resolver) { + return ERR_OK; +} +// +// if (o->resolver) { +// r = cadiz_resolve(o->resolver, o->body, o->body, &out_len); +// if (!r) { +// msg = cmime_message_new(); +// o->subject = o->body + out_len; +// r = cmime_message_from_string(&msg, o->body, 0); +// if (!r) { +// o->subject = cmime_message_get_subject(msg); +// o->subject = cmime_string_strip(o->subject); +// out_len += strlen(o->subject) + 1; +// } +// remaining -= out_len; +// } else { +// remaining -= c; +// } +// } + + +static char *get_message(asn1_node item, char *out_digest, char *out_data, size_t *out_len) { + int r; + size_t c; + asn1_node root; + char err[1024]; + char buf[64]; + char sig[64]; + + memset(&root, 0, sizeof(root)); + r = asn1_array2tree(schema_entry_asn1_tab, &root, err); + if (r != ASN1_SUCCESS) { + debug_log(DEBUG_ERROR, err); + return NULL; + } + + /// \todo there must be a better way to do this + r = asn1_copy_node(root, "Kee.KeeEntry.parent", item, "parent"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + r = asn1_copy_node(root, "Kee.KeeEntry.signatureRequest", item, "signatureRequest"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + r = asn1_copy_node(root, "Kee.KeeEntry.response", item, "response"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } +// +// r = asn1_copy_node(root, "Kee.KeeEntry.signatureResponse", item, "signatureResponse"); +// if (r != ASN1_SUCCESS) { +// printf("%d (%s) %s\n", r, err, asn1_strerror(r)); +// return NULL; +// } + + r = asn1_copy_node(root, "Kee.KeeEntry.timestamp", item, "timestamp"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + r = asn1_copy_node(root, "Kee.KeeEntry.creditDelta", item, "creditDelta"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + r = asn1_copy_node(root, "Kee.KeeEntry.collateralDelta", item, "collateralDelta"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + r = asn1_copy_node(root, "Kee.KeeEntry.body", item, "body"); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + c = 64; + r = asn1_read_value(item, "signatureResponse", sig, (int*)&c); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + buf[0] = 0; + c = 1; + r = asn1_write_value(root, "Kee.KeeEntry.signatureResponse", buf, c); + if (r != ASN1_SUCCESS) { + return NULL; + } + + r = asn1_der_coding(root, "Kee.KeeEntry", out_data, (int*)out_len, err); + if (r != ASN1_SUCCESS) { + printf("%d (%s) %s\n", r, err, asn1_strerror(r)); + return NULL; + } + + r = calculate_digest_algo(out_data, *out_len, out_digest, GCRY_MD_SHA512); + if (r) { + return NULL; + } + + return out_digest; +} + + +static int verify_item(asn1_node item, const char *pubkey_first_data, const char *pubkey_last_data) { + int r; + gcry_sexp_t sig; + gcry_sexp_t msg; + gcry_sexp_t pubkey; + gcry_error_t err; + size_t c; + gcry_mpi_t sr; + gcry_mpi_t ss; + char sig_data[64]; + char msg_data[1024 + 64]; + char *p = (char*)msg_data; + char pubkey_sexp_data[1024]; + size_t pubkey_sexp_len; + + c = 1024; + p = get_message(item, p, p+64, &c); + if (p == NULL) { + return 1; + } + + c = 64; + r = asn1_read_value(item, "signatureResponse", sig_data, (int*)&c); + if (r != ASN1_SUCCESS) { + return 1; + } + + c = 0; + err = gcry_mpi_scan(&sr, GCRYMPI_FMT_STD, sig_data, 32, &c); + if (err != GPG_ERR_NO_ERROR) { + return 1; + } + if (c != 32) { + return 1; + } + + c = 0; + err = gcry_mpi_scan(&ss, GCRYMPI_FMT_STD, sig_data+32, 32, &c); + if (err != GPG_ERR_NO_ERROR) { + return 1; + } + if (c != 32) { + return 1; + } + + c = 0; + err = gcry_sexp_build(&sig, &c, "(sig-val(eddsa(r %m)(s %m)))", sr, ss); + if (err != GPG_ERR_NO_ERROR) { + return 1; + } + + c = 0; + err = gcry_sexp_build(&msg, &c, "(data(flags eddsa)(hash-algo sha512)(value %b))", 64, p); + if (err != GPG_ERR_NO_ERROR) { + return 1; + } + +/// \todo "string too long" error when build same string as can "new" - bug in gcrypt? +// c = 0; +// err = gcry_sexp_build(&pubkey, &c, "(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:%b))))", 32, pubkey_last_data); +// if (err != GPG_ERR_NO_ERROR) { +// return 1; +// } + + strcpy(pubkey_sexp_data, "(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:"); + c = strlen(pubkey_sexp_data); + memcpy(pubkey_sexp_data + c, pubkey_last_data, 32); + strcpy(pubkey_sexp_data + c + 32, "))))"); + + pubkey_sexp_len = c + 32 + 4; + c = 0; + err = gcry_sexp_new(&pubkey, pubkey_sexp_data, pubkey_sexp_len, 1); + if (err != GPG_ERR_NO_ERROR) { + return 1; + } + + return 0; +} + +struct kee_ledger_item_t *kee_ledger_parse_item(struct kee_ledger_t *ledger, const char *data, size_t data_len) { + int r; + int c; + char err[1024]; + asn1_node root; + asn1_node item; + struct kee_ledger_item_t *prev; + struct kee_ledger_item_t *cur; + int *credit_delta; + int *collateral_delta; + const char *pubkey_first; + const char *pubkey_last; + int v; + + + prev = ledger->last_item; + ledger->last_item = calloc(sizeof(struct kee_ledger_item_t), 1); + cur = ledger->last_item; + cur->prev_item = prev; + + memset(&root, 0, sizeof(root)); + memset(&item, 0, sizeof(item)); + r = asn1_array2tree(schema_entry_asn1_tab, &root, err); + if (r != ASN1_SUCCESS) { + debug_log(DEBUG_ERROR, err); + return NULL; + } + + r = asn1_create_element(root, "Kee.KeeEntry", &item); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return NULL; + } + + c = (int)data_len - 1; + if (*(data+c)) { + cur->initiator = BOB; + credit_delta = &cur->bob_credit_delta; + collateral_delta = &cur->bob_collateral_delta; + pubkey_first = (const char*)ledger->pubkey_alice; // alice countersigns bobs + pubkey_last = (const char*)ledger->pubkey_bob; // alice countersigns bobs + } else { + credit_delta = &cur->alice_credit_delta; + collateral_delta = &cur->alice_collateral_delta; + pubkey_first = (const char*)ledger->pubkey_bob; + pubkey_last = (const char*)ledger->pubkey_alice; + } + + r = asn1_der_decoding(&item, data, c, err); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return NULL; + } + + r = verify_item(item, pubkey_first, pubkey_last); + if (r) { + return NULL; + } + + c = sizeof(v); + r = asn1_read_value(item, "creditDelta", &v, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return NULL; + } + + strap_be((char*)&v, c, (char*)credit_delta, sizeof(v)); + if (is_le()) { + flip_endian(sizeof(v), (void*)credit_delta); + } + + c = sizeof(v); + r = asn1_read_value(item, "collateralDelta", &v, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return NULL; + } + + strap_be((char*)&v, c, (char*)collateral_delta, sizeof(v)); + if (is_le()) { + flip_endian(sizeof(v), (void*)collateral_delta); + } + + return cur; +} + +void kee_ledger_item_free(struct kee_ledger_item_t *item) { + if (item->prev_item != NULL) { + kee_ledger_item_free(item->prev_item); + } + free(item); +} + +void kee_ledger_free(struct kee_ledger_t *ledger) { + kee_ledger_item_free(ledger->last_item); +} + +int kee_ledger_parse(struct kee_ledger_t *ledger, const char *data, size_t data_len) { + int r; + char err[1024]; + asn1_node root; + asn1_node item; + int c; + //CMimeMessage_T *msg; + + memset(ledger, 0, sizeof(struct kee_ledger_t)); + memset(&root, 0, sizeof(root)); + memset(&item, 0, sizeof(item)); + r = asn1_array2tree(schema_entry_asn1_tab, &root, err); + if (r != ASN1_SUCCESS) { + debug_log(DEBUG_ERROR, err); + return ERR_FAIL; + } + + r = asn1_create_element(root, "Kee.KeeEntryHead", &item); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + c = (int)data_len; + r = asn1_der_decoding(&item, data, c, err); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + +// r = calculate_digest_algo(data, data_len, o->current_id, GCRY_MD_SHA512); +// if (r) { +// return ERR_DIGESTFAIL; +// } + + c = 64; + r = asn1_read_value(item, "uoa", ledger->uoa, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + c = 1; + r = asn1_read_value(item, "uoaDecimals", &ledger->uoa_decimals, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + c = 32; + r = asn1_read_value(item, "alicePubKey", ledger->pubkey_alice, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + c = 32; + r = asn1_read_value(item, "bobPubKey", ledger->pubkey_bob, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + c = 4096 - 64; + r = asn1_read_value(item, "body", ledger->body, &c); + if (r != ASN1_SUCCESS) { + fprintf(stderr, "%s\n", err); + return r; + } + + return ERR_OK; +} + +// +//int kee_ledger_parse_item(kee_ledger_item_t *item, const char *data, size_t data_len) { +// int r; +// char err[1024]; +// asn1_node root; +// asn1_node item; +// int alice; +// int bob; +// int credit; +// int collateral; +// int c; +// char flag; +// int v; +// char *p; +// +// memset(&root, 0, sizeof(root)); +// memset(&item, 0, sizeof(item)); +// r = asn1_array2tree(schema_entry_asn1_tab, &root, err); +// if (r != ASN1_SUCCESS) { +// debug_log(DEBUG_ERROR, err); +// return ERR_FAIL; +// } +// +// r = asn1_create_element(root, "Kee.KeeEntry", &item); +// if (r != ASN1_SUCCESS) { +// fprintf(stderr, "%s\n", err); +// return r; +// } +// +// c = (int)data_len - 1; +// r = asn1_der_decoding(&item, data, c, err); +// if (r != ASN1_SUCCESS) { +// fprintf(stderr, "%s\n", err); +// return r; +// } +// flag = *(data+data_len-1); +// +//// c = 1; +//// flags = 0; +//// r = asn1_read_value(item, "flags", &flags, &c); +//// if (r != ASN1_SUCCESS) { +//// fprintf(stderr, "%s\n", err); +//// return r; +//// } +// +// credit = 0; +// p = (char*)&v; +// c = sizeof(v); +// v = 0; +// r = asn1_read_value(item, "creditDelta", p, &c); +// if (r != ASN1_SUCCESS) { +// fprintf(stderr, "%s\n", err); +// return r; +// } +// +// strap_be(p, c, (char*)&credit, sizeof(credit)); +// if (is_le()) { +// flip_endian(sizeof(credit), (void*)&credit); +// } +// +// collateral = 0; +// c = sizeof(v); +// v = 0; +// r = asn1_read_value(item, "collateralDelta", p, &c); +// if (r != ASN1_SUCCESS) { +// fprintf(stderr, "%s\n", err); +// return r; +// } +// +// strap_be(p, c, (char*)&collateral, sizeof(collateral)); +// if (is_le()) { +// flip_endian(sizeof(collateral), (void*)&collateral); +// } +// +// +// alice = 0; +// bob = 0; +// if (flag) { // bit string is left to right +// bob = credit; +// } else { +// alice = credit; +// } +// +// return ERR_OK; +//} diff --git a/src/ledger.h b/src/ledger.h @@ -0,0 +1,37 @@ +#ifndef KEE_LEDGER_H_ +#define KEE_LEDGER_H_ + +#include <time.h> + +enum kee_initiator { + ALICE, + BOB, +}; + +struct kee_ledger_item_t { + struct kee_ledger_item_t *prev_item; + int alice_credit_delta; + int bob_credit_delta; + int alice_collateral_delta; + int bob_collateral_delta; + time_t time; + enum kee_initiator initiator; + int response; + const char *body; +}; + +struct kee_ledger_t { + struct kee_ledger_item_t *last_item; + char pubkey_alice[32]; + char pubkey_bob[32]; + char uoa_decimals; + char uoa[64]; + char body[4032]; +}; + +struct kee_ledger_item_t *kee_ledger_parse_item(struct kee_ledger_t *ledger, const char *data, size_t data_len); +int kee_ledger_parse(struct kee_ledger_t *ledger, const char *data, size_t data_len); +void kee_ledger_free(struct kee_ledger_t *ledger); +void kee_ledger_item_free(struct kee_ledger_item_t *item); + +#endif diff --git a/src/tests/Makefile b/src/tests/Makefile @@ -5,7 +5,7 @@ LINKOBJS := $(wildcard ../*.o) INCLUDES := -I.. CFLAGS += `pkg-config --cflags gtk4 gstreamer-1.0` $(INCLUDES) -Wall #LIBS := `pkg-config --libs gtk4 zlib lmdb libgcrypt libxdg-basedir gstreamer-1.0` -lb64 -lvarint -llash -LIBS := `pkg-config --libs gtk4 zlib lmdb libgcrypt libxdg-basedir gstreamer-1.0` -lb64 -llash +LIBS := `pkg-config --libs gtk4 zlib lmdb libgcrypt libxdg-basedir gstreamer-1.0` -lb64 -llash -ltasn1 LDFLAGS += $(LIBS) all: obj_debug $(OBJS) @@ -16,8 +16,11 @@ obj_debug: %.o: %.c $(CC) $(CFLAGS) $< -o test_$* debug.o $(LINKOBJS) $(LDFLAGS) -test_run: $(wildcard test_*) - ./$< +#test_run: $(wildcard test_*) +# ./$< +test_run: + ./test_cadir + ./test_ledger test: all test_run diff --git a/src/tests/ledger.c b/src/tests/ledger.c @@ -0,0 +1,33 @@ +#include <string.h> + +#include "ledger.h" +#include "hex.h" + +const char *test_ledger_data = "30818e0c035553440201020420c042f26197b312fef5def17e8c7f978c3219f74981f8430cd3bd57116014d33904201613476c3986b1317fbd831a07665210a1c271f1d88a04e8aa804bb032b44f6b04403aba8490187f543270b48e770bb272021f8fdb22f584080fc9d4d02553f4504624122d99ffc9254cdb8a2ef3826e47f60e1e7b5ec28b635fbba1141e3f486aad"; + +const char *test_item_data_a = "3082011d044000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020817c7aecdfb6a9cbd020212d8020219600440c333b18dfa822c3ce875de07d54d932ed631aaa7f996153a120bda81e2f0717f4f5e67547019bcd8af0ba6ecba95313bbdc5e43385670d67c24ef76879514ad6044010b17b8c2bc18d7c040f8a4927d20714b78f48f6c3be23867f0ba4aac96b61081f86157942620852c2a47d12a3fb066604018d4228908cf5b0c29568fd67bf0c0101ff0440d6ea4b6501aa6c87cc67bc3297a999a53d8d756873656a0ad68ce9a3b5f9be3d974acf17f4d54d27f9ecf8e854fed32b6c672fa1378142f8898ebd9d68a8540f00"; + + +int main() { + int r; + size_t c; + struct kee_ledger_t ledger; + struct kee_ledger_item_t *ledger_item_a; + char data[1024]; + + c = hex2bin(test_ledger_data, (unsigned char*)data); + r = kee_ledger_parse(&ledger, data, c); + if (r) { + return 1; + } + + c = hex2bin(test_item_data_a, (unsigned char*)data); + ledger_item_a = kee_ledger_parse_item(&ledger, data, c); + if (ledger_item_a == NULL) { + return 1; + } + + kee_ledger_free(&ledger); + + return 0; +}