From 9f485078c0a4306482bd60fa5041eaa7e5d986278c342c3d81fc5f81aa21d2ca Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 27 Mar 2025 18:00:24 +0300 Subject: [PATCH] wip --- py3/cm/av.py | 5 + py3/cm/cmd/__init__.py | 0 py3/cm/cmd/enctool/main.py | 264 ++++++++++++++++++++++ py3/cm/cmd/keytool/__init__.py | 0 py3/cm/cmd/keytool/main.py | 268 +++++++++++++++++++++++ py3/cm/cmd/keytool/test | Bin 0 -> 159 bytes py3/cm/enc/balloon/decap.py | 8 + py3/cm/enc/chapoly/dem.py | 12 + py3/cm/enc/dem.py | 3 + py3/cm/enc/enc.py | 11 + py3/cm/enc/kem.py | 8 + py3/cm/enc/magic.py | 3 + py3/cm/enc/mceliece6960119x25519/algo.py | 3 + py3/cm/enc/mceliece6960119x25519/kp.py | 2 + py3/cm/enc/sntrup4591761x25519/algo.py | 2 + py3/cm/enc/sntrup4591761x25519/kp.py | 2 + py3/cm/hash/algo.py | 22 ++ py3/cm/sign/__init__.py | 14 ++ py3/cm/sign/ed25519_blake2b/25519.py | 4 + py3/cm/sign/ed25519_blake2b/__init__.py | 0 py3/cm/sign/ed25519_blake2b/algo.py | 3 + py3/cm/sign/ed25519_blake2b/ed25519.py | 153 +++++++++++++ py3/cm/sign/ed25519_blake2b/kp.py | 2 + py3/cm/sign/gost/gost.py | 8 + py3/cm/sign/gost/kp.py | 2 + py3/cm/sign/iface.py | 30 +++ py3/cm/sign/prv.py | 9 + py3/cm/sign/pub.py | 92 ++++++++ py3/cm/sign/signed.py | 19 ++ py3/magic.py | 2 + 30 files changed, 951 insertions(+) create mode 100644 py3/cm/av.py create mode 100644 py3/cm/cmd/__init__.py create mode 100644 py3/cm/cmd/enctool/main.py create mode 100644 py3/cm/cmd/keytool/__init__.py create mode 100644 py3/cm/cmd/keytool/main.py create mode 100644 py3/cm/cmd/keytool/test create mode 100644 py3/cm/enc/balloon/decap.py create mode 100644 py3/cm/enc/chapoly/dem.py create mode 100644 py3/cm/enc/dem.py create mode 100644 py3/cm/enc/enc.py create mode 100644 py3/cm/enc/kem.py create mode 100644 py3/cm/enc/magic.py create mode 100644 py3/cm/enc/mceliece6960119x25519/algo.py create mode 100644 py3/cm/enc/mceliece6960119x25519/kp.py create mode 100644 py3/cm/enc/sntrup4591761x25519/algo.py create mode 100644 py3/cm/enc/sntrup4591761x25519/kp.py create mode 100644 py3/cm/hash/algo.py create mode 100644 py3/cm/sign/__init__.py create mode 100644 py3/cm/sign/ed25519_blake2b/25519.py create mode 100644 py3/cm/sign/ed25519_blake2b/__init__.py create mode 100644 py3/cm/sign/ed25519_blake2b/algo.py create mode 100644 py3/cm/sign/ed25519_blake2b/ed25519.py create mode 100644 py3/cm/sign/ed25519_blake2b/kp.py create mode 100644 py3/cm/sign/gost/gost.py create mode 100644 py3/cm/sign/gost/kp.py create mode 100644 py3/cm/sign/iface.py create mode 100644 py3/cm/sign/prv.py create mode 100644 py3/cm/sign/pub.py create mode 100644 py3/cm/sign/signed.py create mode 100644 py3/magic.py diff --git a/py3/cm/av.py b/py3/cm/av.py new file mode 100644 index 0000000..5d257c7 --- /dev/null +++ b/py3/cm/av.py @@ -0,0 +1,5 @@ +class AV: + def __init__(self, a, v): + self.A = a + self.V = bytes(v) + diff --git a/py3/cm/cmd/__init__.py b/py3/cm/cmd/__init__.py new file mode 100644 index 0000000..473a0f4 diff --git a/py3/cm/cmd/enctool/main.py b/py3/cm/cmd/enctool/main.py new file mode 100644 index 0000000..7858eaf --- /dev/null +++ b/py3/cm/cmd/enctool/main.py @@ -0,0 +1,264 @@ +from cm import av +from copy import deepcopy +from cm.sign.prv import PrvMagic +from cm.sign.pub import PubParse, PubLoad, Can, KUKEM as sign_KUKEM +from cm.sign.signed import Signed +import cm.enc.magic as cmenc_magic +from cm.enc.enc import Encrypted +from cm.enc.chapoly.dem import Open, CEKLen, DEMAlgo as chapoly_DEMAlgo +from cm.enc.balloon.decap import Decapsulate, BalloonBLAKE2bHKDF as cmballoon_BalloonBLAKE2bHKDF +from cm.hash.algo import DefaultNumCPU as cmhash_DefaultNumCPU +from cm.enc.sntrup4591761x25519.algo import SNTRUP4591761X25519HKDFBLAKE2b, SNTRUP4591761X25519 +import keks +import hashlib +import logging +import argparse +import os +import getpass +import sys + + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logging.debug("Debug mode") +logging.info("Info message here") +logging.warning("Warning!") +logging.error("Error while running") +logging.critical("Critical error") + +FdPubR = 4 +FdPrvR = 8 + + +def blake2bHash() -> hashlib.blake2b: + try: + h = hashlib.blake2b( + digest_size=512 + ) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + return h + + +def readPasswd(prompt: str) -> bytearray: + try: + if raw := os.environ["CMENCTOOL_PASSPHRASE"]: + return bytearray(raw) + else: + if sys.stdin.isatty(): + p = getpass.getpass(prompt) + else: + print(prompt) + p = sys.stdin.readline().rstrip() + return bytearray(p) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + + +def parsePrv(data) -> tuple[av.AV, bytearray]: + magic = None + #magic, data = keks.StripMagic(data) - нет такого метода + if magic == PrvMagic: + pass + elif magic == cmenc_magic.Magic: + encrypted = Encrypted() + try: + d = keks.NewDecoderFromBytes(data) # нет такого метода + d.DecodeStruct(encrypted) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + try: + if encrypted.DEM.A != chapoly_DEMAlgo: + logging.error("unsupported prv encryption DEM") + if len(encrypted.kem) != 1 or encrypted.kem[0].A != cmballoon_BalloonBLAKE2bHKDF or len(encrypted.Payload) == 0: + logging.error("wrong prv encryption KEM") + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + passwd = readPasswd("Passphrase for private key:") + #cek = bytearray() + try: + cek = Decapsulate(encrypted.KEM[0], encrypted.Id[:], passwd) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + buf = None + try: + buf = Open(buf, encrypted.Payload, cek, 1) + return #? + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + # дописать + pass + else: + logging.error("wrong magic") + try: + d = keks.NewDecoderFromBytes(data, keks.DecodeOpts(MaxStrLen=10e16)) + d.DecodeStruct(av) + tail = d.B + return #? + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--id", + type=str, + default="", + action="store", + dest="setSalt", + help="Set that /id instead of autogeneration", + ) + parser.add_argument( + "--include-to", + type=bool, + default=False, + action="store", + dest="includeTo", + help='Include "to" field in KEMs', + ) + parser.add_argument( + "--p", + type=bool, + default=False, + action="store", + dest="passphrase", + help="Use passphrase", + ) + parser.add_argument( + "--balloon-s", + type=int, + default=2**16, + action="store", + dest="balloonS", + help="Balloon's space cost", + ) + parser.add_argument( + "--balloon-t", + type=int, + default=4, + action="store", + dest="balloonT", + help="Balloon's time cost", + ) + parser.add_argument( + "--balloon-p", + type=int, + default=2, + action="store", + dest="balloonP", + help="Balloon's number of threads", + ) + parser.add_argument( + "--d", + type=bool, + default=False, + action="store", + dest="doDecrypt", + ) + parser.add_argument( + "--parallel", + type=int, + default=cmhash_DefaultNumCPU, + action="store", + dest="doDecrypt", + help="Parallel cryptors", + ) + parser.add_argument( + "--embed", + type=bool, + default=False, + action="store", + dest="noblob", + help="Include payload into container", + ) + args = parser.parse_args() + + try: + with open(FdPubR, "rb") as f: + pubs = [] # list of cm.AV + pubIds = [] # list of bytearrays + + if data := f.read(): + while len(data) > 0: + signed = None # Signed from signed.py + signed, data = PubParse(data) + load = PubLoad() + if not load.Can(sign_KUKEM): + logging.error(f"public key: {len(pubs)} does not have {sign_KUKEM} key usage") + if len(load.Pub) != 1: + logging.critical(f"public key {len(pubs)}: expected single public key") + pubs.append(load.Pub[0]) + pubIds.append(load.Id) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + + try: + with open(FdPrvR, "rb") as f: + prvs = [] # list of cm.AV + + if data := f.read(): + while len(data) > 0: + av = None + av, data = parsePrv(data) + prvs.append(prvs, av) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + logging.critical(f"private key: {len(prvs)}: {e.__class__.__name__}") + + if args.doDecrypt: + try: + d = keks.newDecoderFromReader(sys.stdin, keks.MagicDecodeOpts) + t = d.DecodeAtom() + if not isinstance(t, cmenc_magic.Magic): + logging.critical("no magic met") + if not isinstance(d.Iter().Magic(), cmenc_magic.Magic): + logging.critical("wrong magic") + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + + try: + encrypted = None + d = keks.NewDecoderFromReader(sys.stdin, None) + d.DecodeStruct(encrypted) + except Exception as e: + logging.error(f"An error occurred: {e.__class__.__name__}") + + if encrypted.DEM.A != chapoly_DEMAlgo: + logging.error(f"unsupported DEM: {encrypted.DEM.A}") + + if len(encrypted.KEM) == 0: + logging.error("no KEMs") + + try: + for kemIdx, kem in enumerate(encrypted.KEM): + #case + if kem.A == cmballoon_BalloonBLAKE2bHKDF: + if not args.passphrase: + logging.error(f"{kemIdx}, {kem.A}, skipping because no -passwd") + + passwd = readPasswd(f"Passphrase for {kemIdx} KEM:") + cek = Decapsulate(kem, encrypted.Id[:], passwd) + + if len(cek) != CEKLen: + logging.error(f"{kemIdx}, {kem.A}, wrong key len, skipping") + + #case + elif kem.A == SNTRUP4591761X25519HKDFBLAKE2b: + if len(prvs) == 0: + logging.info(f"{kemIdx}, {kem.A}, skipping because no private key specified") + if kem.Encap is None: + logging.error("missing encap") + + if len(kem.Encap) != sntrup4591761.CiphertextSize + 32: # найти реализацию пакета + logging.error("invalid encap len") + + for prv in prvs: + if prv.A != SNTRUP4591761X25519: + continue + if len(prv.V) != sntrup4591761.PrivateKeySize + 32: # найти реализацию пакета + logging.error("invalid private key len") + ourSNTRUP = deepcopy() diff --git a/py3/cm/cmd/keytool/__init__.py b/py3/cm/cmd/keytool/__init__.py new file mode 100644 index 0000000..473a0f4 diff --git a/py3/cm/cmd/keytool/main.py b/py3/cm/cmd/keytool/main.py new file mode 100644 index 0000000..2ed56cb --- /dev/null +++ b/py3/cm/cmd/keytool/main.py @@ -0,0 +1,268 @@ +# keytool -- dealing with KEKS-encoded keypairs utility + +import cm.enc.mceliece6960119x25519.algo as mceliece6960119x25519 +import cm.enc.sntrup4591761x25519.algo as sntrup4591761x25519 +import cm.hash.algo as cmhash +import keks +from cm import av +from cm.enc.mceliece6960119x25519.kp import NewKeypair as mceliece6960119x25519_NewKeypair +from cm.enc.sntrup4591761x25519.kp import NewKeypair as sntrup4591761x25519_NewKeypair +from cm.hash.algo import ByName as cmhash_ByName +from cm.sign.ed25519_blake2b import algo +from cm.sign.ed25519_blake2b.kp import NewKeypair as ed25519blake2b_NewKeypair +from cm.sign.gost import gost +from cm.sign.ed25519_blake2b.ed25519 import Ed25519BLAKE2b +from cm.sign.gost.kp import NewKeypair as gost_NewKeypair +from cm.sign.pub import CertificationVerify +from cm.sign.pub import PubMagic +from cm.sign.pub import PubParse +from cm.sign.signed import CertifyWith +from cm.sign.signed import Load +from cm.sign.signed import Signed + +from sys import stdin +from sys import stdout +import argparse +from datetime import datetime +from datetime import timedelta +from datetime import timezone +import logging +from hashlib import blake2b +from uuid import uuid4 + + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", +) + +FdPubR = 4 +FdPubW = 5 +FdPrvR = 8 +FdPrvW = 9 + + +def PubParse(data): + signed, tail = keks.loads(data) + if signed["load"]["t"] != "pub": + raise ValueError("TODO: not pub") + return signed, tail + # тут должны проверить типа напротив схемы + + +def PrvParse(data): + av, tail = keks.loads(data) + if len(tail) > 0: + raise ValueError("prv has tail") + # тут должны проверить типа напротив схемы + if av["a"] == algo.Ed25519BLAKE2b: + return Ed25519BLAKE2b(av["v"], None) + # TODO + # if av["a"] == GOST + # return Ed25519BLAKE2b(av["v"]) + raise ValueError("unknown prv algorigthm") + + +def CertifyWith(pubLoad, caPubLoad, caPrv, since, till): + tbs = { + "cid": uuid4(), + "sid": caPubLoad["id"], + "exp": [since, till], + } + signAlgo, sign = caPrv.sign(keks.dumps(pubLoad) + keks.dumps(tbs)) + return { "tbs": tbs, "sign": {"a": signAlgo, "v": sign}, } + + +# /load || sig-tbs + + +def mustReadAll(fd) -> bytes: + with open(fd, "rb") as f: + data = f.read() + return data + + +def main(): + parser = argparse.ArgumentParser() + + ku = dict() + sub = dict() + + # python main.py --ku sig --ku kem --ku ca + parser.add_argument( + "--ku", + type=str, + action="append", + ) + + # python main.py --sub n=ya.ru --sub c=ru --sub org=atlas + parser.add_argument( + "--sub", + type=str, + action="append", + ) + + parser.add_argument( + "--since", + default="", + dest='sinceRaw', + help="Optional notBefore, \"2006-01-02 15:04:05\" format", + ) + parser.add_argument( + "--lifetime", + action='store', + default=365, + help="Lifetime of the certification, days", + ) + parser.add_argument( + "--algo", + action='store', + default=algo.Ed25519BLAKE2b, + help="Public key algorithm", + ) + parser.add_argument( + "--verify", + action='store', + default=False, + help="Verify provided -pub with -ca-pub", + ) + parser.add_argument( + "--list-algo", + action='store', + default=False, + dest='doList', + help="List available algorithms", + ) + + args = parser.parse_args() + + if args.ku: + for _ku in args.ku: + ku[_ku] = None + + if args.sub: + for _sub in args.sub: + s = (_sub).split('=', maxsplit=2) # TODO: проверить sub как список + if len(s) != 2: + raise("invalid key=value") + + sub[s[0]] = s[1] + if args.doList: + algos = [ + algo.Ed25519BLAKE2b, + gost.GOST3410256A, + gost.GOST3410512C, + #sntrup4591761x25519.SNTRUP4591761X25519, + #mceliece6960119x25519.ClassicMcEliece6960119X25519, + ] + algos.sort() + for _alg in algos: + print(_alg) + + fdPubR = FdPubR + fdPubW = FdPubW + fdPrvR = FdPrvR + fdPrvW = FdPrvW + + doCertify = len(sub) == 0 and not args.verify + + since = datetime.time + if args.sinceRaw == "": + since = datetime.now() + if args.sinceRaw is None or args.sinceRaw == "": + since = datetime.now(timezone.utc).replace( + microsecond=0) # Текущая дата и время, округленная до секунды + else: + since = datetime.strptime(args.sinceRaw, "%Y-%m-%d %H:%M:%S") # Парсинг даты + + till = since + timedelta(days=args.lifetime) # Вычисление даты окончания + + caPrv = None + caPubs = [] # list of Signed + if doCertify or args.verify: + data = mustReadAll(fdPubR) + while len(data) > 0: + signed, data = PubParse(data) + caPubs.append(signed) + if doCertify: + caPrv = PrvParse(mustReadAll(fdPrvR)) + + signed = Signed() + if doCertify or args.verify: + signed, _ = PubParse(mustReadAll(stdin.fileno())) + + if args.verify: + CertificationVerify(caPubs, datetime.now(timezone.utc)) + + prv = None + pubLoad = dict() + if doCertify: + pubLoad = signed["load"]["v"] + else: # создание шаблона + pub = bytearray() + if args.algo == algo.Ed25519BLAKE2b: + prvRaw, pub = Ed25519BLAKE2b.generate_keypair() + elif args.algo == gost.GOST3410256A or args.algo == gost.GOST3410512C: + _, prvRaw, pub = gost_NewKeypair(args.algo) + elif args.algo == sntrup4591761x25519.SNTRUP4591761X25519: + prvRaw, pub = sntrup4591761x25519_NewKeypair() + elif args.algo == mceliece6960119x25519.ClassicMcEliece6960119X25519: + prvRaw, pub = mceliece6960119x25519_NewKeypair() + + prv = { "a": args.algo, "v": prvRaw } + pubLoad = { + "sub": sub, + "pub": [ { "a" : args.algo, "v" : pub, } ], + } + hasher = None # в Go это билдин интерфейс + + if args.algo == algo.Ed25519BLAKE2b or args.algo == sntrup4591761x25519.SNTRUP4591761X25519: + hasher = blake2b(digest_size=32) + elif args.algo == gost.GOST3410256A or args.algo == gost.GOST3410512C: + hasher = cmhash_ByName(cmhash.Streebog256) + elif args.algo == mceliece6960119x25519.ClassicMcEliece6960119X25519: + hasher = cmhash_ByName(cmhash.SHAKE128) + + #keks.loads(hasher, pubLoad["pub"], None) + hasher.update(keks.dumps((pubLoad["pub"]))) + pubLoad["id"] = hasher.digest() + + if len(ku) > 0: + pubLoad["ku"] = ku + + pubLoadAny = None # Go any(pubLoad) - какой-то интерфейс встроенный + signed = { + "load": { + "t": "pub", + "v": pubLoad, + } + } + + if doCertify: + signed["sigs"] = [CertifyWith( + signed["load"], + caPubs[0]["load"]["v"], + caPrv, + since, + till, + )] + stdout.buffer.write(keks.dumps(signed)) + else: + with open(FdPubW, "wb") as fd: + fd.write(keks.dumps(signed)) + if prv is not None: + with open(FdPrvW, "wb") as fd: + fd.write(keks.dumps(prv)) + +# tmp_buf = None # по сути этот буфер нужен для временного хранения набора байтов +# +# keks.loads(tmp_buf, PubMagic, None) +# +# keks.loads(tmp_buf, signed, None) +# + #if _, err = io.Copy(fdPubW, &buf); err != nil { + # log.Fatal(err) # копирование из буффера в файл (в файловый дескриптор как файл)s + +if __name__ == "__main__": + main() + diff --git a/py3/cm/cmd/keytool/test b/py3/cm/cmd/keytool/test new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..d8e3273545ae9fb74bc477bbc06b8a6eeb4bc6ead3822f8039f78b32270a4006 GIT binary patch literal 159 zcmd-)l9Qj9!g;Xda6xI(!7|Q6nJIgjr=B?6<5+mz^_loCb0@Cw2I~&{B0k4lcF(bDvpYk@rt~r)BHRY?D)?Dt|Qv6eQMN*v!CixVSV4 LsP|wJ0}ucJq>x6W literal 0 HcmV?d00001 diff --git a/py3/cm/enc/balloon/decap.py b/py3/cm/enc/balloon/decap.py new file mode 100644 index 0000000..288251c --- /dev/null +++ b/py3/cm/enc/balloon/decap.py @@ -0,0 +1,8 @@ +BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf" +SaltLen = 16 +HKDFInfo = "cm/encrypted/balloon-blake2b-hkdf" + + + +def Decapsulate(): + pass diff --git a/py3/cm/enc/chapoly/dem.py b/py3/cm/enc/chapoly/dem.py new file mode 100644 index 0000000..9ef102e --- /dev/null +++ b/py3/cm/enc/chapoly/dem.py @@ -0,0 +1,12 @@ +import hashlib + + +ChunkLen = 128 * 1024 +CommitmentLen = 32 +CEKLen = hashlib.blake2b.digest_size +DEMAlgo = "chapoly-krkc" + + + +def Open(): + pass diff --git a/py3/cm/enc/dem.py b/py3/cm/enc/dem.py new file mode 100644 index 0000000..830bfa4 --- /dev/null +++ b/py3/cm/enc/dem.py @@ -0,0 +1,3 @@ +class DEM: + def __init__(self): + self.A = "" diff --git a/py3/cm/enc/enc.py b/py3/cm/enc/enc.py new file mode 100644 index 0000000..c1457e6 --- /dev/null +++ b/py3/cm/enc/enc.py @@ -0,0 +1,11 @@ +from .dem import DEM +from .kem import KEM +import uuid + + +class Encrypted: + def __init__(self): + self.dem = DEM() + self.kem = KEM() + self.Payload = bytearray() + self.Id = uuid.UUID() diff --git a/py3/cm/enc/kem.py b/py3/cm/enc/kem.py new file mode 100644 index 0000000..e2a8522 --- /dev/null +++ b/py3/cm/enc/kem.py @@ -0,0 +1,8 @@ +class KEM: + def __init__(self): + self.A = "" + self.CEK = bytearray() + self.To = bytearray() + self.BallonCost = None # + self.salt = bytearray() # + self.Encap = bytearray() # diff --git a/py3/cm/enc/magic.py b/py3/cm/enc/magic.py new file mode 100644 index 0000000..787c248 --- /dev/null +++ b/py3/cm/enc/magic.py @@ -0,0 +1,3 @@ +import keks + +Magic = keks.Magic("cm/encrypted") diff --git a/py3/cm/enc/mceliece6960119x25519/algo.py b/py3/cm/enc/mceliece6960119x25519/algo.py new file mode 100644 index 0000000..a4851c6 --- /dev/null +++ b/py3/cm/enc/mceliece6960119x25519/algo.py @@ -0,0 +1,3 @@ +ClassicMcEliece6960119X25519 = "mceliece6960119-x25519" +ClassicMcEliece6960119X25519HKDFSHAKE256 = "mceliece6960119-x25519-hkdf-shake256" + diff --git a/py3/cm/enc/mceliece6960119x25519/kp.py b/py3/cm/enc/mceliece6960119x25519/kp.py new file mode 100644 index 0000000..baf38f1 --- /dev/null +++ b/py3/cm/enc/mceliece6960119x25519/kp.py @@ -0,0 +1,2 @@ +def NewKeypair(): + pass diff --git a/py3/cm/enc/sntrup4591761x25519/algo.py b/py3/cm/enc/sntrup4591761x25519/algo.py new file mode 100644 index 0000000..78901c3 --- /dev/null +++ b/py3/cm/enc/sntrup4591761x25519/algo.py @@ -0,0 +1,2 @@ +SNTRUP4591761X25519 = "sntrup4591761-x25519" +SNTRUP4591761X25519HKDFBLAKE2b = "sntrup4591761-x25519-hkdf-blake2b" diff --git a/py3/cm/enc/sntrup4591761x25519/kp.py b/py3/cm/enc/sntrup4591761x25519/kp.py new file mode 100644 index 0000000..baf38f1 --- /dev/null +++ b/py3/cm/enc/sntrup4591761x25519/kp.py @@ -0,0 +1,2 @@ +def NewKeypair(): + pass diff --git a/py3/cm/hash/algo.py b/py3/cm/hash/algo.py new file mode 100644 index 0000000..7154f6e --- /dev/null +++ b/py3/cm/hash/algo.py @@ -0,0 +1,22 @@ +import os + + +BLAKE2b = "blake2b" +BLAKE2b256 = "blake2b256" +SHA2512 = "sha2-512" +SHAKE128 = "shake128" +SHAKE256 = "shake256" +Streebog256 = "streebog256" +Streebog512 = "streebog512" + +BLAKE2bMerkle = "blake2b-merkle" +SHAKE128Merkle = "shake128-merkle" +SHAKE256Merkle = "shake256-merkle" +Streebog256Merkle = "streebog256-merkle" +Streebog512Merkle = "streebog512-merkle" + +DefaultNumCPU = max(1, os.cpu_count() // 2) + + +def ByName(): + pass diff --git a/py3/cm/sign/__init__.py b/py3/cm/sign/__init__.py new file mode 100644 index 0000000..474c82e --- /dev/null +++ b/py3/cm/sign/__init__.py @@ -0,0 +1,14 @@ +from abc import ABCMeta +from abc import abstractmethod + + +class Signer(metaclass=ABCMeta): + @abstractmethod + def generate_keypair(self) -> [bytes, bytes]: + """TODO + """ + + @abstractmethod + def sign(self, sk: bytes, data: bytes) -> bytes: + """TODO + """ diff --git a/py3/cm/sign/ed25519_blake2b/25519.py b/py3/cm/sign/ed25519_blake2b/25519.py new file mode 100644 index 0000000..18de89b --- /dev/null +++ b/py3/cm/sign/ed25519_blake2b/25519.py @@ -0,0 +1,4 @@ +class Ed25519BLAKE2b: + def __init__(self): + pass + diff --git a/py3/cm/sign/ed25519_blake2b/__init__.py b/py3/cm/sign/ed25519_blake2b/__init__.py new file mode 100644 index 0000000..473a0f4 diff --git a/py3/cm/sign/ed25519_blake2b/algo.py b/py3/cm/sign/ed25519_blake2b/algo.py new file mode 100644 index 0000000..fc76c88 --- /dev/null +++ b/py3/cm/sign/ed25519_blake2b/algo.py @@ -0,0 +1,3 @@ +Ed25519BLAKE2b = "ed25519-blake2b" +Ed25519PhBLAKE2b = "ed25519ph-blake2b" +Ed25519PhBLAKE2bMerkle = "ed25519ph-blake2b-merkle" diff --git a/py3/cm/sign/ed25519_blake2b/ed25519.py b/py3/cm/sign/ed25519_blake2b/ed25519.py new file mode 100644 index 0000000..b3c8ca0 --- /dev/null +++ b/py3/cm/sign/ed25519_blake2b/ed25519.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +from hashlib import blake2b +from os import urandom + +from cm.sign import Signer + + +b = 256 +q = 2**255 - 19 +l = 2**252 + 27742317777372353535851937790883648493 + + +def H(m): + return blake2b(m).digest() + + +def expmod(b, e, m): + if e == 0: + return 1 + t = expmod(b, e//2, m)**2 % m + if e & 1 == 1: + t = (t*b) % m + return t + + +def inv(x): + return expmod(x, q-2, q) + + +d = -121665 * inv(121666) +I = expmod(2, (q-1)//4, q) + + +def xrecover(y): + xx = (y*y-1) * inv(d*y*y+1) + x = expmod(xx, (q+3)//8, q) + if (x*x - xx) % q != 0: + x = (x*I) % q + if x % 2 != 0: + x = q-x + return x + + +By = 4 * inv(5) +Bx = xrecover(By) +B = (Bx % q, By % q) + + +def edwards(P, Q): + x1, y1 = P + x2, y2 = Q + x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) + y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) + return (x3 % q, y3 % q) + + +def scalarmult(P, e): + if e == 0: + return (0, 1) + Q = scalarmult(P, e//2) + Q = edwards(Q, Q) + if e & 1 == 1: + Q = edwards(Q, P) + return Q + + +def encodeint(y): + return y.to_bytes(32, "little") + + +def decodeint(s): + return int.from_bytes(s, byteorder="little") + + +def Hint(m): + return decodeint(H(m)) + + +def encodepoint(P): + x, y = P + return ((y & ((1 << 255)-1)) | ((x & 1) << 255)).to_bytes(32, "little") + + +def bit(h, i): + return (bytearray(h)[i//8] >> (i % 8)) & 1 + + +def publickey(sk): + h = H(sk) + a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2)) + A = scalarmult(B, a) + return encodepoint(A) + + +def signature(m, sk, pk): + h = bytearray(H(sk)) + a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2)) + r = Hint(bytes(bytearray([h[i] for i in range(b//8, b//4)])) + m) + R = scalarmult(B, r) + S = (r + Hint(encodepoint(R) + pk + m) * a) % l + return encodepoint(R) + encodeint(S) + + +def isoncurve(P): + x, y = P + return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0 + + +def decodepoint(s): + y = sum(2**i * bit(s, i) for i in range(0, b-1)) + x = xrecover(y) + if x & 1 != bit(s, b-1): + x = q-x + P = (x, y) + if not isoncurve(P): + raise ValueError("decoding point that is not on curve") + return P + + +def checkvalid(s, m, pk): + if len(s) != b//4: + raise ValueError("signature length is wrong") + if len(pk) != b//8: + raise ValueError("public-key length is wrong") + try: + R = decodepoint(s[0:b//8]) + A = decodepoint(pk) + S = decodeint(s[b//8:b//4]) + except ValueError: + return False + h = Hint(encodepoint(R) + pk + m) + return scalarmult(B, S) == edwards(R, scalarmult(A, h)) + + +class Ed25519BLAKE2b(Signer): + def __init__(self, sk, pk): + self.sk = sk + if pk is None: + self.pk = publickey(sk) + + @classmethod + def generate_keypair(cls): + sk = urandom(32) + pk = publickey(sk) + return sk, pk + + def sign(self, data): + if self.sk is None: + raise ValueError("no prv") + from sys import stderr + print("WE ARE SIGNING:", data.hex(), self.pk.hex(), file=stderr) + return "ed25519-blake2b", signature(data, self.sk, self.pk) diff --git a/py3/cm/sign/ed25519_blake2b/kp.py b/py3/cm/sign/ed25519_blake2b/kp.py new file mode 100644 index 0000000..baf38f1 --- /dev/null +++ b/py3/cm/sign/ed25519_blake2b/kp.py @@ -0,0 +1,2 @@ +def NewKeypair(): + pass diff --git a/py3/cm/sign/gost/gost.py b/py3/cm/sign/gost/gost.py new file mode 100644 index 0000000..4440c3c --- /dev/null +++ b/py3/cm/sign/gost/gost.py @@ -0,0 +1,8 @@ +GOST3410256A = "gost3410-256A" +GOST3410512C = "gost3410-512C" +GOST3410256AMerkle = "gost3410-256A-merkle" +GOST3410512CMerkle = "gost3410-512C-merkle" + + +def CurveByName(): + pass diff --git a/py3/cm/sign/gost/kp.py b/py3/cm/sign/gost/kp.py new file mode 100644 index 0000000..baf38f1 --- /dev/null +++ b/py3/cm/sign/gost/kp.py @@ -0,0 +1,2 @@ +def NewKeypair(): + pass diff --git a/py3/cm/sign/iface.py b/py3/cm/sign/iface.py new file mode 100644 index 0000000..c52ff5f --- /dev/null +++ b/py3/cm/sign/iface.py @@ -0,0 +1,30 @@ +from abc import ABCMeta +from abc import abstractmethod + +# class Iface: +# def __init__(self): +# self.Signer = None + +# def SetMode(): +# pass + +# def Prehasher(): +# pass + +# def Algo(): +# pass + +# def Mode(): +# pass + + +class Signer(metaclass=ABCMeta): + @abstractmethod + def generate_keypair(self) -> [bytes, bytes]: + """TODO + """ + + @abstractmethod + def sign(self, sk: bytes, data: bytes) -> bytes: + """TODO + """ diff --git a/py3/cm/sign/prv.py b/py3/cm/sign/prv.py new file mode 100644 index 0000000..24dccec --- /dev/null +++ b/py3/cm/sign/prv.py @@ -0,0 +1,9 @@ +import keks + + + +PrvMagic = keks.Magic("cm/prv") + + +def PrvParse(): + pass diff --git a/py3/cm/sign/pub.py b/py3/cm/sign/pub.py new file mode 100644 index 0000000..9f22898 --- /dev/null +++ b/py3/cm/sign/pub.py @@ -0,0 +1,92 @@ +from keks import Magic +from magic import StripMagic +from .signed import Signed, SignedParse + + +KUCA = "ca" #// CA-capable key usage +KUSig = "sig" #// Signing-capable key usage +KUKEM = "kem" #// Key-encapsulation-mechanism key usage +PubMagic = Magic("cm/pub") +FPRLen = 32 #// fingerprint's length + + +def PubParse(data: bytes) -> tuple[Signed, bytes]: + magic, data = StripMagic(data) + try: + if magic != "" and magic != PubMagic: + raise Exception("Wrong magic") + else: + signed, tail = SignedParse(data) + except Exception as e: + print(e) + try: + signed, tail = SignedParse(data) + except Exception as e: + print(e) + # = PubParse() внизу + return signed, tail + + +# добавить перегруженную функцию PubParse аналогично след +def PubParse(): + pass +""" +func (signed *Signed) PubParse() error { + if signed.Load.T != "pub" { + return errors.New("PubParse: wrong load type") + } + for _, sig := range signed.Sigs { + if _, ok := sig.TBS["cid"]; !ok { + return errors.New("PubParse: missing cid") + } + if _, ok := sig.TBS["exp"]; !ok { + return errors.New("PubParse: missing exp") + } + } + if signed.Load.V == nil { + return errors.New("PubParse: missing /load/v") + } + var load PubLoad + var err error + if v, ok := (*signed.Load.V).(map[string]any); ok { + mapAny := any(v) + signed.Load.V = &mapAny + err = keks.Map2Struct(&load, v) + } else { + err = errors.New("PubParse: wrong /load/v") + } + if err != nil { + return err + } + if len(load.Sub) == 0 { + return errors.New("PubParse: empty sub") + } + if len(load.Crit) != 0 { + return errors.New("PubParse: currently no critical extensions are supported") + } + if len(load.Pub) == 0 { + return errors.New("PubParse: empty pub") + } + if len(load.Id) != FPRLen { + return errors.New("PubParse: invalid id len") + } + for _, pub := range load.Pub { + if len(pub.A) == 0 || len(pub.V) == 0 { + return errors.New("PubParse: non-filled pub") + } + } + return nil +} +""" + + +def CertificationVerify(): + pass + + +def PubLoad(): + pass + + +def Can(): + pass diff --git a/py3/cm/sign/signed.py b/py3/cm/sign/signed.py new file mode 100644 index 0000000..7635033 --- /dev/null +++ b/py3/cm/sign/signed.py @@ -0,0 +1,19 @@ +class Load: + def __init__(self): + self.V = None + self.T = "" + + +class Signed: + def __init__(self): + self.load = Load() + self.pubs = [] + self.sigs = [] + + +def CertifyWith(): + pass + + +def SignedParse(data: bytes) -> tuple[Signed, bytes]: + pass diff --git a/py3/magic.py b/py3/magic.py new file mode 100644 index 0000000..f3e9626 --- /dev/null +++ b/py3/magic.py @@ -0,0 +1,2 @@ +def StripMagic(): + pass -- 2.50.0