kee

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

commit 72a51a39b92f5fdbebe419a8d1520069c87324fc
parent e20165682c89111d65db5554332faf0058dd209f
Author: lash <dev@holbrook.no>
Date:   Fri,  5 Apr 2024 15:48:34 +0100

Add key generation to testdata generator

Diffstat:
Msrc/gpg.c | 16+++++++++-------
Mtestdata_ng.py | 415+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
2 files changed, 386 insertions(+), 45 deletions(-)

diff --git a/src/gpg.c b/src/gpg.c @@ -215,6 +215,8 @@ static int key_from_path(gcry_sexp_t *key, const char *p, const char *passphrase /** * \todo consistent endianness for key length in persistent storage (fwrite) + * \todo implement MAC + * \todo test new data length location (part of ciphertext) */ static int key_create(gcry_sexp_t *key, const char *p, const char *passphrase) { int r; @@ -230,7 +232,6 @@ static int key_create(gcry_sexp_t *key, const char *p, const char *passphrase) { char v[BUFLEN]; char nonce[CHACHA20_NONCE_LENGTH_BYTES]; - e = gcry_sexp_new(&in, (const void*)sexp_quick, strlen(sexp_quick), 0); if (e) { printf("error sexp: %s\n", gcry_strerror(e)); @@ -241,7 +242,8 @@ static int key_create(gcry_sexp_t *key, const char *p, const char *passphrase) { printf("error gen: %s\n", gcry_strerror(e)); return (int)e; } - kl = gcry_sexp_sprint(*key, GCRYSEXP_FMT_CANON, v, BUFLEN); + kl = gcry_sexp_sprint(*key, GCRYSEXP_FMT_CANON, v+sizeof(int), BUFLEN); + memcpy(v, &kl, sizeof(int)); c = get_padsize(kl, ENCRYPT_BLOCKSIZE); char ciphertext[c]; @@ -257,11 +259,11 @@ static int key_create(gcry_sexp_t *key, const char *p, const char *passphrase) { return ERR_KEYFAIL; } l = c; - c = fwrite(&kl, sizeof(int), 1, f); - if (c != 1) { - fclose(f); - return ERR_KEYFAIL; - } +// c = fwrite(&kl, sizeof(int), 1, f); +// if (c != 1) { +// fclose(f); +// return ERR_KEYFAIL; +// } c = fwrite(nonce, CHACHA20_NONCE_LENGTH_BYTES, 1, f); if (c != 1) { fclose(f); diff --git a/testdata_ng.py b/testdata_ng.py @@ -7,6 +7,33 @@ from Crypto.Cipher import ChaCha20_Poly1305 from Crypto.PublicKey import ECC import Crypto.IO.PKCS8 import Crypto.Util.asn1 +import lmdb +import time +import shutil +import email.message +import random +from faker import Faker +from faker.providers import lorem +import varint + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +fake = Faker() +fake.add_provider(lorem) + +SIGNS_ALICE_CREDIT_DELTA_NEGATIVE = 1 << 0 +SIGNS_BOB_CREDIT_DELTA_NEGATIVE = 1 << 1 +SIGNS_ALICE_COLLATERAL_DELTA_NEGATIVE = 1 << 2 +SIGNS_BOB_COLLATERAL_DELTA_NEGATIVE = 1 << 3 + +FLAGS_SIGNER_IS_BOB = 1 << 0 + +NOBODY = b'\x00' * 64 +NOSIG = b'\x00' * 65 +PFX_LEDGER_HEAD = b'\x01' +PFX_LEDGER_ENTRY = b'\x02' +PFX_LEDGER_COUNTERKEY = b'\x03' def padbytes(b, padsize=4096): @@ -18,41 +45,353 @@ h = hashlib.new('sha256') h.update(b'1234') z = h.digest() -k = ECC.generate(curve='Ed25519') -pk_pkcs8 = k.export_key(format='DER') -pk_der = Crypto.IO.PKCS8.unwrap(pk_pkcs8) -pk = Crypto.Util.asn1.DerOctetString().decode(pk_der[1], strict=True).payload -pubk = k.public_key().export_key(format='raw') - -w = io.BytesIO() -w.write(b"(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:") -w.write(pubk) -w.write(b")))(11:private-key(3:ecc(5:curve7:Ed25519)(1:q32:") -w.write(pubk) -w.write(b")(1:d32:") -w.write(pk) -w.write(b"))))") -b = w.getvalue() -l = len(b) -bl = l.to_bytes(4, byteorder='little') - -nonce = os.urandom(12) -cph = ChaCha20_Poly1305.new(key=z, nonce=nonce) -r = cph.encrypt(bl + b) -r = padbytes(r) -sys.stdout.buffer.write(nonce + r) - -#tmpl = (8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:\xae3\xe12\xec\x9e:\xa3-\xa3\x0b\x122}\xbc\xdb\xd8\xdc\x03\xea\x989D[S\xbaocs\xfb\x00\xce)))(11:private-key(3:ecc(5:curve7:Ed25519)(1:q32:\xae3\xe12\xec\x9e:\xa3-\xa3\x0b\x122}\xbc\xdb\xd8\xdc\x03\xea\x989D[S\xbaocs\xfb\x00\xce)(1:d32:k\x90\x88\xb5\x8cyn\xef]b\xd8\x80\x19\xd1\xf8\xda\xe2\xc0\x1b\xe9V\t\x07h7\x05\xb7\xd8\x85bu0)))) - - -# b'(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:\xae3\xe12\xec\x9e:\xa3-\xa3\x0b\x122}\xbc\xdb\xd8\xdc\x03\xea\x989D[S\xbaocs\xfb\x00\xce)))(11:private-key(3:ecc(5:curve7:Ed25519)(1:q32:\xae3\xe12\xec\x9e:\xa3-\xa3\x0b\x122}\xbc\xdb\xd8\xdc\x03\xea\x989D[S\xbaocs\xfb\x00\xce)(1:d32:k\x90\x88\xb5\x8cyn\xef]b\xd8\x80\x19\xd1\xf8\xda\xe2\xc0\x1b\xe9V\t\x07h7\x05\xb7\xd8\x85bu0))))' - -#f = open('key.bin', 'rb') -#l = int.from_bytes(f.read(4), byteorder='little') -#nonce = f.read(12) -#ctxt = f.read() -#f.close() -# -#cph = ChaCha20_Poly1305.new(key=z, nonce=nonce) -#txt = cph.decrypt(ctxt) -#print(txt[:l]) +def alice(w): + k = ECC.generate(curve='Ed25519') + pk_pkcs8 = k.export_key(format='DER') + pk_der = Crypto.IO.PKCS8.unwrap(pk_pkcs8) + pk = Crypto.Util.asn1.DerOctetString().decode(pk_der[1], strict=True).payload + pubk = k.public_key().export_key(format='raw') + + wt = io.BytesIO() + wt.write(b"(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:") + wt.write(pubk) + wt.write(b")))(11:private-key(3:ecc(5:curve7:Ed25519)(1:q32:") + wt.write(pubk) + wt.write(b")(1:d32:") + wt.write(pk) + wt.write(b"))))") + b = wt.getvalue() + l = len(b) + bl = l.to_bytes(4, byteorder='little') + + nonce = os.urandom(12) + cph = ChaCha20_Poly1305.new(key=z, nonce=nonce) + r = cph.encrypt(bl + b) + r = padbytes(r) + w.write(nonce + r) + + return pubk + + +def bob(d): + k = ECC.generate(curve='Ed25519') + pubk = k.public_key().export_key(format='raw') + + + wt = io.BytesIO() + wt.write(b"(8:key-data(10:public-key(3:ecc(5:curve7:Ed25519)(1:q32:") + wt.write(pubk) + wt.write(b"))))") + b = wt.getvalue() + + env = lmdb.open(d) + dbi = env.open_db() + + k = PFX_LEDGER_COUNTERKEY = b'\x03' + + with env.begin(write=True) as tx: + tx.put(k + pubk, b'bob') + + return pubk + + +def db_init(d): + d = os.path.join(d, 'testdata_mdb') + logg.info('using d for db' + d) + + try: + shutil.rmtree(d) + except FileNotFoundError: + pass + os.makedirs(d) + return d + + +def to_absflag(v): + flag = False + if v < 0: + flag = True + return (abs(v), flag,) + + +class LedgerContent(email.message.EmailMessage): + + def __init__(self): + super(LedgerContent, self).__init__() + self.set_default_type("text/plain") + self.add_header("Subject", fake.sentence()) + self.set_content(fake.paragraph()) + + + def kv(self): + b = self.as_bytes() + h = hashlib.new("sha512") + h.update(b) + z = h.digest() + return (z, b,) + + +class LedgerHeadContent(LedgerContent): + pass + + +class LedgerEntryContent(LedgerContent): + pass + + +class LedgerHead: + + def __init__(self, alice_key=None, bob_key=None, body=NOBODY): + self.uoa = "USD" + self.uoa_decimals = 2 + if alice_key == None: + alice_key = os.urandom(65) + self.alice_pubkey_ref = alice_key + if bob_key == None: + bob_key = os.urandom(65) + self.bob_pubkey_ref = bob_key + logg.info('new ledger header with alice {} bob {}'.format(self.alice_pubkey_ref.hex(), self.bob_pubkey_ref.hex())) + self.body = LedgerHeadContent() + + + def __serialize_add(self, b, w): + c = varint.encode(len(b)) + w.write(c) + w.write(b) + + + def __data_add(self, data_dir, k, v): + fp = os.path.join(data_dir, k.hex()) + f = open(fp, 'wb') + #logg.info("fp {}".format(fp)) + f.write(v) + f.close() + + + def serialize(self, data_dir, w=sys.stdout.buffer): + b = self.uoa.encode('utf-8') + self.__serialize_add(b, w) + + b = varint.encode(self.uoa_decimals) + self.__serialize_add(b, w) + + b = self.alice_pubkey_ref + self.__serialize_add(b, w) + + b = self.bob_pubkey_ref + self.__serialize_add(b, w) + + (k, b) = self.body.kv() + self.__data_add(data_dir, k, b) + self.__serialize_add(k, w) + + + @staticmethod + def to_key(b): + r = b'' + r += PFX_LEDGER_HEAD + v = time.time_ns() + b = v.to_bytes(8, byteorder='big') + r += b + + return r + + +class LedgerEntry: + + credit_delta_min = -1000 + credit_delta_max = 1000 + collateral_delta_min = 0 + collateral_delta_max = 0 + + def __init__(self, head, parent=None, body=NOBODY, signer=None): + random.seed(int(time.time_ns())) + self.head = head + self.flags = 0 + self.signs = 0 + self.parent = parent + if self.parent == None: + self.parent = b'\x00' * 64 + self.timestamp = time.time_ns() + + self.body = LedgerEntryContent() + + v = random.randint(self.credit_delta_min, self.credit_delta_max) + self.flags = v % 2 + (v, neg) = to_absflag(v) + self.credit_delta = v + if neg: + if self.flags: + self.signs |= SIGNS_BOB_CREDIT_DELTA_NEGATIVE + else: + self.signs |= SIGNS_ALICE_CREDIT_DELTA_NEGATIVE + + v = random.randint(self.collateral_delta_min, self.collateral_delta_max) + self.response_value = v % 2 + (v, neg) = to_absflag(v) + self.collateral_delta = v + if neg: + if self.flags: + self.signs |= SIGNS_BOB_COLLATERAL_DELTA_NEGATIVE + else: + self.signs |= SIGNS_ALICE_COLLATERAL_DELTA_NEGATIVE + + #self.request_signature = NOSIG + #self.response_signature = NOSIG + self.request_signature = os.urandom(65) + self.response_signature = os.urandom(65) + self.signer = signer + + + def __serialize_add(self, b, w): + c = varint.encode(len(b)) + w.write(c) + w.write(b) + + + def __data_add(self, data_dir, k, v): + fp = os.path.join(data_dir, k.hex()) + f = open(fp, 'wb') + #logg.info("fp {}".format(fp)) + f.write(v) + f.close() + + + def serialize(self, data_dir, w=sys.stdout.buffer): + b = self.flags.to_bytes(1) + self.__serialize_add(b, w) + + b = self.parent + self.__serialize_add(b, w) + + b = self.timestamp.to_bytes(8, byteorder='big') + self.__serialize_add(b, w) + + b = self.signs.to_bytes(1) + self.__serialize_add(b, w) + +# realvalue = self.credit_delta +# if self.flags & FLAGS_SIGNER_IS_BOB: +# if (self.signs & SIGNS_BOB_CREDIT_DELTA_NEGATIVE): +# realvalue *= -1 +# else: +# if (self.signs & SIGNS_ALICE_CREDIT_DELTA_NEGATIVE): +# realvalue *= -1 + + + b = varint.encode(self.credit_delta) + if self.flags: + self.__serialize_add(varint.encode(0), w) + self.__serialize_add(b, w) + if not self.flags: + self.__serialize_add(varint.encode(0), w) + + + #if self.flags: + # self.__serialize_add(b'\x00', w) + #logg.debug('encode flags {} credit {} collateral {}'.format(self.flags, self.credit_delta, self.collateral_delta)) + b = varint.encode(self.collateral_delta) + if not self.flags: + self.__serialize_add(varint.encode(0), w) + self.__serialize_add(b, w) + if not self.flags: + self.__serialize_add(varint.encode(0), w) + + #if self.signer != None: + # self.signature = self.signer(b) + + (k, b) = self.body.kv() + self.__data_add(data_dir, k, b) + self.__serialize_add(k, w) + + self.__serialize_add(self.request_signature, w) + + b = self.response_value.to_bytes(1) + self.__serialize_add(b, w) + + self.__serialize_add(self.response_signature, w) + + return b + + + @staticmethod + def to_key(v, k): + r = b'' + r += PFX_LEDGER_ENTRY + r += k + ts = v[68:68+8] + #logg.debug('ts {}: of {}'.format(ts.hex(), v.hex())) + r += ts + return r + + +def generate_entry(data_dir, head, parent): + o = LedgerEntry(head, parent=parent) + w = io.BytesIO() + r = o.serialize(data_dir, w=w) + h = hashlib.new('sha512') + b = w.getvalue() + h.update(b) + z = h.digest() + return (z, b,) + + +def generate_ledger(data_dir, entry_count=3, alice=None, bob=None): + r = [] + o = LedgerHead(alice_key=alice, bob_key=bob) + w = io.BytesIO() + o.serialize(data_dir, w=w) + h = hashlib.new('sha512') + b = w.getvalue() + h.update(b) + z = h.digest() + r.append((z, b,)) + + k = z + parent = None + for i in range(entry_count): + v = generate_entry(data_dir, k, parent=parent) + # \todo generate key value already here + parent = v[0] + r.append(v) + + return r + + +if __name__ == '__main__': + d = os.path.dirname(__file__) + data_dir = os.path.join(d, 'testdata_resource') + try: + shutil.rmtree(data_dir) + except FileNotFoundError: + pass + os.makedirs(data_dir) + + d = db_init(d) + + f = open('key.bin', 'wb') + alice = alice(f) + f.close() + bob = bob(d) + + print("bo {}\nalice {}\n".format(alice.hex(), bob.hex())) + + env = lmdb.open(d) + dbi = env.open_db() + + count_ledgers = os.environ.get('COUNT', '1') + + with env.begin(write=True) as tx: + for i in range(int(count_ledgers)): + c = random.randint(1, 20) + r = generate_ledger(data_dir, entry_count=c, alice=alice, bob=bob) + + v = r.pop(0) + + z = v[0] + k = LedgerHead.to_key(v[0]) + tx.put(k, v[1]) + + for v in r: + k = LedgerEntry.to_key(v[1], z) + tx.put(k, v[1])