]> Cypherpunks repositories - keks.git/commitdiff
wip pykeytool
authorSergey Matveev <stargrave@stargrave.org>
Thu, 27 Mar 2025 15:00:24 +0000 (18:00 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 27 Mar 2025 15:00:24 +0000 (18:00 +0300)
30 files changed:
py3/cm/av.py [new file with mode: 0644]
py3/cm/cmd/__init__.py [new file with mode: 0644]
py3/cm/cmd/enctool/main.py [new file with mode: 0644]
py3/cm/cmd/keytool/__init__.py [new file with mode: 0644]
py3/cm/cmd/keytool/main.py [new file with mode: 0644]
py3/cm/cmd/keytool/test [new file with mode: 0644]
py3/cm/enc/balloon/decap.py [new file with mode: 0644]
py3/cm/enc/chapoly/dem.py [new file with mode: 0644]
py3/cm/enc/dem.py [new file with mode: 0644]
py3/cm/enc/enc.py [new file with mode: 0644]
py3/cm/enc/kem.py [new file with mode: 0644]
py3/cm/enc/magic.py [new file with mode: 0644]
py3/cm/enc/mceliece6960119x25519/algo.py [new file with mode: 0644]
py3/cm/enc/mceliece6960119x25519/kp.py [new file with mode: 0644]
py3/cm/enc/sntrup4591761x25519/algo.py [new file with mode: 0644]
py3/cm/enc/sntrup4591761x25519/kp.py [new file with mode: 0644]
py3/cm/hash/algo.py [new file with mode: 0644]
py3/cm/sign/__init__.py [new file with mode: 0644]
py3/cm/sign/ed25519_blake2b/25519.py [new file with mode: 0644]
py3/cm/sign/ed25519_blake2b/__init__.py [new file with mode: 0644]
py3/cm/sign/ed25519_blake2b/algo.py [new file with mode: 0644]
py3/cm/sign/ed25519_blake2b/ed25519.py [new file with mode: 0644]
py3/cm/sign/ed25519_blake2b/kp.py [new file with mode: 0644]
py3/cm/sign/gost/gost.py [new file with mode: 0644]
py3/cm/sign/gost/kp.py [new file with mode: 0644]
py3/cm/sign/iface.py [new file with mode: 0644]
py3/cm/sign/prv.py [new file with mode: 0644]
py3/cm/sign/pub.py [new file with mode: 0644]
py3/cm/sign/signed.py [new file with mode: 0644]
py3/magic.py [new file with mode: 0644]

diff --git a/py3/cm/av.py b/py3/cm/av.py
new file mode 100644 (file)
index 0000000..5d257c7
--- /dev/null
@@ -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 (file)
index 0000000..473a0f4
diff --git a/py3/cm/cmd/enctool/main.py b/py3/cm/cmd/enctool/main.py
new file mode 100644 (file)
index 0000000..7858eaf
--- /dev/null
@@ -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 (file)
index 0000000..473a0f4
diff --git a/py3/cm/cmd/keytool/main.py b/py3/cm/cmd/keytool/main.py
new file mode 100644 (file)
index 0000000..2ed56cb
--- /dev/null
@@ -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 (file)
index 0000000..d8e3273
Binary files /dev/null and b/py3/cm/cmd/keytool/test differ
diff --git a/py3/cm/enc/balloon/decap.py b/py3/cm/enc/balloon/decap.py
new file mode 100644 (file)
index 0000000..288251c
--- /dev/null
@@ -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 (file)
index 0000000..9ef102e
--- /dev/null
@@ -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 (file)
index 0000000..830bfa4
--- /dev/null
@@ -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 (file)
index 0000000..c1457e6
--- /dev/null
@@ -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 (file)
index 0000000..e2a8522
--- /dev/null
@@ -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 (file)
index 0000000..787c248
--- /dev/null
@@ -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 (file)
index 0000000..a4851c6
--- /dev/null
@@ -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 (file)
index 0000000..baf38f1
--- /dev/null
@@ -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 (file)
index 0000000..78901c3
--- /dev/null
@@ -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 (file)
index 0000000..baf38f1
--- /dev/null
@@ -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 (file)
index 0000000..7154f6e
--- /dev/null
@@ -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 (file)
index 0000000..474c82e
--- /dev/null
@@ -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 (file)
index 0000000..18de89b
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..fc76c88
--- /dev/null
@@ -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 (file)
index 0000000..b3c8ca0
--- /dev/null
@@ -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 (file)
index 0000000..baf38f1
--- /dev/null
@@ -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 (file)
index 0000000..4440c3c
--- /dev/null
@@ -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 (file)
index 0000000..baf38f1
--- /dev/null
@@ -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 (file)
index 0000000..c52ff5f
--- /dev/null
@@ -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 (file)
index 0000000..24dccec
--- /dev/null
@@ -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 (file)
index 0000000..9f22898
--- /dev/null
@@ -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 (file)
index 0000000..7635033
--- /dev/null
@@ -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 (file)
index 0000000..f3e9626
--- /dev/null
@@ -0,0 +1,2 @@
+def StripMagic():
+    pass