]> Cypherpunks repositories - keks.git/commitdiff
Add Magic
authorSergey Matveev <stargrave@stargrave.org>
Wed, 22 Jan 2025 12:07:32 +0000 (15:07 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 22 Jan 2025 14:39:04 +0000 (17:39 +0300)
56 files changed:
c/cmd/deatomiser/deatomiser.c
c/cmd/pp/pp.c
c/cmd/test-vector/test-vector.c
c/doc/atom.texi
c/lib/atom.h
c/lib/dec.c
c/lib/enc.c
c/lib/enc.h
c/lib/err.c
c/lib/err.h
c/lib/items.c
go/atom-decode.go
go/atom-encode.go
go/atomtype_string.go
go/cmd/pp/main.go
go/cmd/test-vector-anys/main.go
go/cmd/test-vector-manual/main.go
go/cmd/textdump-tester/main.go
go/encode.go
go/iter.go
go/magic.go [new file with mode: 0644]
go/pki/algo.go
go/pki/cer.go
go/pki/cmd/certool/basic.t
go/pki/cmd/certool/main.go
go/pki/cmd/enctool/chapoly.go
go/pki/cmd/enctool/main.go
go/pki/cmd/sigtool/main.go
go/pki/prv.go
go/pki/signed.go [moved from go/pki/signed-data.go with 70% similarity]
go/type.go
go/types/type.go
go/types/type_string.go
go/unmarshal.go
py3/keks.py
py3/test-vector.py
py3/tests/strategies.py
py3/tests/test_magic.py [new file with mode: 0644]
py3/tests/textdump-tester
spec/encoding/index.texi
spec/encoding/magic.texi [new file with mode: 0644]
spec/encoding/table.texi
spec/format/cer.texi
spec/format/encrypted.cddl [moved from spec/format/enveloped-data.cddl with 98% similarity]
spec/format/encrypted.texi [moved from spec/format/enveloped-data.texi with 81% similarity]
spec/format/hashed.cddl [moved from spec/format/hashed-data.cddl with 91% similarity]
spec/format/hashed.texi [moved from spec/format/hashed-data.texi with 71% similarity]
spec/format/index.texi
spec/format/private-key.texi
spec/format/registry.texi
spec/format/signed-tbs.cddl [moved from spec/format/signed-data-tbs.cddl with 78% similarity]
spec/format/signed.cddl [moved from spec/format/signed-data.cddl with 86% similarity]
spec/format/signed.texi [moved from spec/format/signed-data.texi with 76% similarity]
tcl/keks.tcl
tcl/mk-fuzz-inputs
tcl/test-vector.tcl

index 50c3966b6f702f758f1e6ee793140635eb07494cc3b5d1cbbe24cf3a8dfe05a2..6e6c5a8e9da8270098afe6a493cfa4657ee9dd146e87d6c2eb4986150480155d 100644 (file)
@@ -13,6 +13,7 @@
 // You should have received a copy of the GNU Lesser General Public
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -79,6 +80,17 @@ main(int argc, char **argv)
             UUIDPrint(atom.v.uuid);
             fputs("\n", stdout);
             break;
+        case KEKSItemMagic:
+            fputs("Magic(", stdout);
+            for (size_t i = 0; i < atom.v.str.len; i++) {
+                if (isprint(atom.v.str.ptr[i]) == 0) {
+                    printf("\\x%02x", atom.v.str.ptr[i]);
+                } else {
+                    fputc(atom.v.str.ptr[i], stdout);
+                }
+            }
+            fputs(")\n", stdout);
+            break;
         case KEKSItemPint:
             fprintf(stdout, "%zu\n", atom.v.pint);
             break;
index 25763e889db1e309ef89546d82f730235325430893928f6e93ef094709de32b1..717893bbadad7efa01b2002fa566591c2cd9cb2156ef3e2da0cbc5e5c00c2aea 100644 (file)
@@ -14,6 +14,7 @@
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <getopt.h>
 #include <math.h>
@@ -112,6 +113,17 @@ printer( // NOLINT(misc-no-recursion)
         UUIDPrint(item->atom.v.uuid);
         fputs("\n", stdout);
         break;
+    case KEKSItemMagic:
+        fputs("Magic(", stdout);
+        for (size_t i = 0; i < item->atom.v.str.len; i++) {
+            if (isprint(item->atom.v.str.ptr[i]) == 0) {
+                printf("\\x%02x", item->atom.v.str.ptr[i]);
+            } else {
+                fputc(item->atom.v.str.ptr[i], stdout);
+            }
+        }
+        fputs(")\n", stdout);
+        break;
     case KEKSItemPint:
         fprintf(stdout, "%zu\n", item->atom.v.pint);
         break;
@@ -341,6 +353,7 @@ main(int argc, char **argv)
     size_t off = 0;
     struct timespec started;
     struct timespec finished;
+AfterMagic:
     errno = 0;
     if (clock_gettime(CLOCK_MONOTONIC_PRECISE, &started) != 0) {
         fprintf(stderr, "clock_gettime(started): %s\n", strerror(errno));
@@ -356,6 +369,9 @@ main(int argc, char **argv)
         fprintf(stderr, "err: %s\n", KEKSErr2Str(err));
         return EXIT_FAILURE;
     }
+    if ((items.len == 1) && (items.list[0].atom.typ == KEKSItemMagic)) {
+        goto AfterMagic;
+    }
     if (!onlyTotals) {
         setenv("TZ", "UTC", 1);
         err = printer(&items, 0, 0, false, 0, NULL);
@@ -363,6 +379,13 @@ main(int argc, char **argv)
             fprintf(stderr, "err: %s\n", KEKSErr2Str(err));
             return EXIT_FAILURE;
         }
+        if ((items.len > 1) && (items.list[0].atom.typ == KEKSItemMagic)) {
+            err = printer(&items, 1, 0, false, 0, NULL);
+            if (err != KEKSErrNo) {
+                fprintf(stderr, "err: %s\n", KEKSErr2Str(err));
+                return EXIT_FAILURE;
+            }
+        }
     }
     if (!noTotals) {
         printf(
index d8251ed0f6eca37bb884bf9918aa4bb0d8a7ef2180454dcd7081ec4d99c9fd31..d16c1412357ed88808950ecb8478f6afdc1e73e54ded001cb8dbbc4bf254b557 100644 (file)
@@ -29,6 +29,12 @@ main(void)
     unsigned char *bin = malloc((uint32_t)1 << (uint8_t)17);
     assert(bin != NULL);
 
+    {
+        unsigned char *magic = (unsigned char *)"test-vector";
+        adder(KEKSAtomMagicEncode(
+            &Got, buf + Off, len - Off, magic, strlen((const char *)magic)));
+    }
+
     adder(KEKSAtomMapEncode(&Got, buf + Off, len - Off)); // .
 
     adder(
index 22718faf8439881781ef763662c63996f27a6097d3a70b2cc9b58fb6c2004d9f..51ba86c2f0330faa474000415e837d0cdb21a22ad37b17da4447246fdd311f6f 100644 (file)
@@ -15,6 +15,7 @@
 @DOCSTRING KEKSAtomNILEncode@
 @DOCSTRING KEKSAtomBoolEncode@
 @DOCSTRING KEKSAtomUUIDEncode@
+@DOCSTRING KEKSAtomMagicEncode@
 @DOCSTRING KEKSAtomUintEncode@
 @DOCSTRING KEKSAtomSintEncode@
 @DOCSTRING KEKSAtomListEncode@
index f747b18120a3d4cd747fb0bf23bd8c0023c856a66400b3aafaf224d3cb6531ec..a0f91f5db34d9c7053b47174fefd91155f2a7c10721553d215e3dab411efa5c5 100644 (file)
@@ -23,6 +23,7 @@ enum KEKSAtomType {
     KEKSAtomTAI64 = 0x18,
     KEKSAtomTAI64N = 0x19,
     KEKSAtomTAI64NA = 0x1A,
+    KEKSAtomMagic = 0x4B,
 
     KEKSAtomStrings = 0x80,
     KEKSAtomIsUTF8 = 0x40,
@@ -64,6 +65,7 @@ enum KEKSItemType {
     KEKSItemBlob,
     KEKSItemFloat,
     KEKSItemTAI64,
+    KEKSItemMagic,
     KEKSItemBin,
     KEKSItemStr,
     KEKSItemRaw,
@@ -94,7 +96,7 @@ enum KEKSItemType {
 //     @code{.chunkLen} is the length of the chunk. @code{.chunks} is
 //     the number of chunks, including the terminating binary string.
 // @item .v.str
-//     @code{.ptr} points to the start of the binary/UTF-8/TAI64*
+//     @code{.ptr} points to the start of the binary/UTF-8/TAI64*/Magic
 //     string. @code{.len} is its length in bytes.
 //     Raw values use it as a payload.
 // @end table
index b844af380d3d42b751ce7d237cb2f8c8f150eaa971ab4664b5269034cf5c0a14..93a2f2a23c4671e46e312a70a37b9015cb3cfd6769f38cd79aff2ddc6de4a9f2 100644 (file)
@@ -117,6 +117,26 @@ KEKSAtomDecode( // NOLINT(misc-no-recursion)
         }
         atom->v.uuid = buf + 1;
         break;
+    case KEKSAtomMagic:
+        atom->typ = KEKSItemMagic;
+        (*got) += 15;
+        if (len < (*got)) {
+            return KEKSErrNotEnough;
+        }
+        if (memcmp(buf, (const unsigned char *)"KEKS", 4) != 0) {
+            return KEKSErrBadMagic;
+        }
+        atom->v.str.ptr = buf + 4;
+        {
+            size_t l = 0;
+            for (; l < 12; l++) {
+                if (buf[4 + l] == 0) {
+                    break;
+                }
+            }
+            atom->v.str.len = l;
+        }
+        break;
 
     case KEKSAtomList:
         atom->typ = KEKSItemList;
index f18a92b6386b805b2ca822efe0d1bc2b532f4f1277bc621dedf3bec80ec4a1ad..ff30afc614c96807633edd401661b3408d6801a8cab1833201323e4abba4dc37 100644 (file)
@@ -71,6 +71,29 @@ KEKSAtomUUIDEncode(
     return true;
 }
 
+bool
+KEKSAtomMagicEncode(
+    size_t *len,
+    unsigned char *buf,
+    const size_t cap,
+    const unsigned char *v,
+    const size_t vlen)
+{
+    if (vlen > 12) {
+        return false;
+    }
+    (*len) = 1 + 15;
+    if (cap < (1 + 15)) {
+        return false;
+    }
+    buf[0] = 'K';
+    buf[1] = 'E';
+    buf[2] = 'K';
+    buf[3] = 'S';
+    memcpy(buf + 4, v, vlen);
+    return true;
+}
+
 static bool
 keksAtomIntEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64_t v)
 {
index 3527c8f93bfbc02bac1998eca9805e56eaba3416c9f5f1e4eac0ca22d2647f4a..1256646a87ccbef79ee6e5f305782a7399c12b6042c69bf858cb6b135fb81ab5 100644 (file)
@@ -49,6 +49,22 @@ KEKSAtomUUIDEncode(
     const size_t cap,
     const unsigned char v[16]);
 
+// TEXINFO: KEKSAtomMagicEncode
+// @deftypefun bool KEKSAtomMagicEncode @
+//     (size_t *len, unsigned char *buf, const size_t cap, @
+//      const unsigned char *v, const size_t vlen)
+// Encode Magic atom in provided @var{buf} with capacity of @var{cap}.
+// In case of success, true is returned and @var{len} will hold how many
+// bytes were written to buffer.
+// @end deftypefun
+bool
+KEKSAtomMagicEncode(
+    size_t *len,
+    unsigned char *buf,
+    const size_t cap,
+    const unsigned char *v,
+    const size_t vlen);
+
 // TEXINFO: KEKSAtomUintEncode
 // @deftypefun bool KEKSAtomUintEncode @
 //     (size_t *len, unsigned char *buf, const size_t cap, const uint64_t v)
index 51fe6493db6ab76fcb29f20b43bdfa70b0231f2b4da6e08116f24a61d80dbdf5..c4b1b75579a75d48fa9785bc264cee857bc948ed363ed201aefecd7c0f615a9c 100644 (file)
@@ -48,6 +48,8 @@ KEKSErr2Str(const enum KEKSErr err)
         return "DeepRecursion";
     case KEKSErrUnexpectedEOC:
         return "UnexpectedEOC";
+    case KEKSErrBadMagic:
+        return "BadMagic";
     default:
         return "unknown";
     }
index c6d59e6e2f1b5dc18e4248f8e1f003e7da5484786e6bdd5090d9ee46acc7e78d..f3b60f620b4cde062016dad9ed4a9aafae3ca361563aae93352c99bcf74598b1 100644 (file)
@@ -41,6 +41,8 @@
 //     Too deep recursion involved during parsing.
 // @item KEKSErrUnexpectedEOC
 //     Unexpected EOC met.
+// @item KEKSErrBadMagic
+//     Wrong magic value.
 // @end table
 // @end deftp
 enum KEKSErr {
@@ -66,6 +68,7 @@ enum KEKSErr {
     KEKSErrUnsatisfiedSchema,
     KEKSErrDeepRecursion,
     KEKSErrUnexpectedEOC,
+    KEKSErrBadMagic,
 };
 
 // TEXINFO: KEKSErr2Str
index 763b94428d5cd0cdd0788e2a630664fac103c5fcc5b4b7ff46d0708884618641..4483c35b9e303a72cc3ae77c2349ab79a9442723bbc3c9e66de34316eeb23d9f 100644 (file)
@@ -308,6 +308,10 @@ KEKSItemsEncode( // NOLINT(misc-no-recursion)
     case KEKSItemUUID:
         ok = KEKSAtomUUIDEncode(&got, buf + *off, cap - (*off), item->atom.v.uuid);
         break;
+    case KEKSItemMagic:
+        ok = KEKSAtomMagicEncode(
+            &got, buf + *off, cap - (*off), item->atom.v.str.ptr, item->atom.v.str.len);
+        break;
     case KEKSItemPint:
         ok = KEKSAtomUintEncode(&got, buf + *off, cap - (*off), item->atom.v.pint);
         break;
index 17e15f39dadc90f994e9cbb20ce1090c62cff0adde31b524c9d725101fc8f0ab..7bfd0a37d5b199d73b1e8f04d6a0931ebdb43d0ce24096fc57639d2a8b552199 100644 (file)
@@ -34,6 +34,7 @@ var (
        ErrUnknownType   = errors.New("unknown type")
        ErrBadUTF8       = errors.New("invalid UTF-8")
        ErrBadInt        = errors.New("bad int value")
+       ErrBadMagic      = errors.New("bad magic value")
 )
 
 func (ctx *Decoder) DecodeAtom() (t types.Type, err error) {
@@ -243,6 +244,18 @@ func (ctx *Decoder) DecodeAtom() (t types.Type, err error) {
                        tai := tai64n.TAI64NA(unsafe.Slice(unsafe.StringData(s), 16))
                        ctx.tai64nas = append(ctx.tai64nas, tai)
                }
+       case AtomMagic:
+               var s string
+               s, err = ctx.getBytes(15)
+               if err != nil {
+                       return
+               }
+               if s[:3] != "EKS" {
+                       err = ErrBadMagic
+                       return
+               }
+               t = types.Magic
+               ctx.strs = append(ctx.strs, strings.TrimRight(s[3:], "\x00"))
        default:
                err = ErrUnknownType
                return
index d69a46e76b75b7703f7053c489c8804a0f0e95cc934607a9501d00882bf4a226..88d818cdb98b5c368ea33a39584260cb056a9fe89ccdef64fb575085dd5af944 100644 (file)
@@ -17,6 +17,7 @@ package keks
 
 import (
        "bytes"
+       "errors"
        "io"
        "math/big"
 
@@ -54,6 +55,20 @@ func UUIDEncode(w io.Writer, v *uuid.UUID) (written int64, err error) {
        return io.Copy(w, bytes.NewReader(append([]byte{byte(AtomUUID)}, v[:]...)))
 }
 
+// Write an encoded Magic atom.
+func MagicEncode(w io.Writer, m Magic) (written int64, err error) {
+       if len(m) > 12 {
+               return 0, errors.New("too long magic")
+       }
+       var v [16]byte
+       v[0] = 'K'
+       v[1] = 'E'
+       v[2] = 'K'
+       v[3] = 'S'
+       copy(v[4:], []byte(m))
+       return io.Copy(w, bytes.NewReader(v[:]))
+}
+
 func atomUintEncode(w io.Writer, v uint64) (written int64, err error) {
        if v == 0 {
                return BinEncode(w, []byte{})
index 8a29a79f9d98ec869beeb161f71fdcf0e1c36cb275ae0dddec12208a5af06e07..dfb719f03b6cfbca34d7432a1884a22543422dd77020ff686e2259d21d3cf988 100644 (file)
@@ -26,6 +26,7 @@ func _() {
        _ = x[AtomTAI64-24]
        _ = x[AtomTAI64N-25]
        _ = x[AtomTAI64NA-26]
+       _ = x[AtomMagic-75]
 }
 
 const (
@@ -34,6 +35,7 @@ const (
        _AtomType_name_2 = "AtomBLOBAtomPIntAtomNInt"
        _AtomType_name_3 = "AtomFloat16AtomFloat32AtomFloat64AtomFloat128AtomFloat256"
        _AtomType_name_4 = "AtomTAI64AtomTAI64NAtomTAI64NA"
+       _AtomType_name_5 = "AtomMagic"
 )
 
 var (
@@ -60,6 +62,8 @@ func (i AtomType) String() string {
        case 24 <= i && i <= 26:
                i -= 24
                return _AtomType_name_4[_AtomType_index_4[i]:_AtomType_index_4[i+1]]
+       case i == 75:
+               return _AtomType_name_5
        default:
                return "AtomType(" + strconv.FormatInt(int64(i), 10) + ")"
        }
index 197232f5ab2c9721cd35706920e09de717d6449c69b7e1feaf190e332c5344f7..00b73d8a0147c99a0985a7c1ad8659c851b3832380b3c6bd19cfa7adf7aec426 100644 (file)
@@ -137,6 +137,16 @@ func printer(iter *keks.Iterator, count int, inList, inMap bool) {
                case types.TAI64NA:
                        t := iter.TAI64NA()
                        fmt.Printf("TAI64NA(%s)\n", hexenc(t[:]))
+               case types.Magic:
+                       var builder strings.Builder
+                       for _, b := range []byte(iter.Magic()) {
+                               if strconv.IsPrint(rune(b)) {
+                                       builder.WriteByte(b)
+                               } else {
+                                       builder.WriteString(fmt.Sprintf("\\x%02x", b))
+                               }
+                       }
+                       fmt.Printf("Magic(%s)\n", builder.String())
                case types.Bin:
                        printbin(iter.Bin())
                case types.Str:
@@ -160,15 +170,21 @@ func printer(iter *keks.Iterator, count int, inList, inMap bool) {
 
 func main() {
        flag.Parse()
-       ctx := keks.NewDecoderFromReader(
-               bufio.NewReader(os.Stdin),
-               &keks.DecodeOpts{SaveOffsets: true},
-       )
-       _, err := ctx.Parse()
-       if err != nil {
-               log.Fatal(err)
+       br := bufio.NewReader(os.Stdin)
+       var off int64
+       for {
+               ctx := keks.NewDecoderFromReader(br, &keks.DecodeOpts{SaveOffsets: true})
+               ctx.Read = off
+               t, err := ctx.Parse()
+               if err != nil {
+                       log.Fatal(err)
+               }
+               iter := ctx.Iter()
+               printer(iter, 1, false, false)
+               if t != types.Magic {
+                       fmt.Println(ctx.Read, "bytes")
+                       break
+               }
+               off += ctx.Read
        }
-       iter := ctx.Iter()
-       printer(iter, 1, false, false)
-       fmt.Println(ctx.Read, "bytes")
 }
index ffb653d6c0669fd1903cc89d951d55ee401c5ce7e9af157ca1ad251d25d01ffb..dced9365d022c53972323cfde9952153f0f21f79140f2591e2bfdb33565fbdc2 100644 (file)
@@ -99,7 +99,11 @@ func main() {
                "uuid": uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367"),
        }
        var buf bytes.Buffer
-       _, err := keks.Encode(&buf, data, nil)
+       _, err := keks.Encode(&buf, keks.Magic("test-vector"), nil)
+       if err != nil {
+               log.Fatal(err)
+       }
+       _, err = keks.Encode(&buf, data, nil)
        if err != nil {
                log.Fatal(err)
        }
index 3eb3794dd5528c263bcfe89ad811416db6485a5e0336c1d47d2982647a794a4a..0f1c04344fcae93616ad12643bd7d21a45b583fe72a3da8d89f43c17e277e69b 100644 (file)
@@ -33,6 +33,7 @@ func mustEncode(n int64, err error) {
 
 func main() {
        var buf bytes.Buffer
+       mustEncode(keks.MagicEncode(&buf, keks.Magic("test-vector")))
        {
                mustEncode(keks.ByteEncode(&buf, byte(keks.AtomMap)))
                {
index 717738d96877f2ac87f9c76af3a7e630f890f66c807edd1025dc97de4417027c..cab6ad996c40c6d3ed9426249b15e257759a9afc4db4ac789e865c5608f8e927 100644 (file)
@@ -120,6 +120,18 @@ func checker(v any) {
                if *our != their {
                        log.Fatalln("TAI64NA differs:", our, their)
                }
+       case "MAGIC":
+               our, ok := v.(keks.Magic)
+               if !ok {
+                       log.Fatalf("expected Magic, got %+v\n", v)
+               }
+               var their string
+               if len(fields) > 1 {
+                       their = string(mustDecodeHex(fields[1]))
+               }
+               if our != keks.Magic(their) {
+                       log.Fatalln("MAGIC differs:", our, their)
+               }
        case "BIN":
                our, ok := v.([]byte)
                if !ok {
index 2f9cf018dc6f016ea7cc37cd08c4b0b8b2d0230f316aafa074d43c414a3d9e9e..5e01ecc18a4a603ec430146544d2698978b15fbcacf8e4028636323f8cd32f0a 100644 (file)
@@ -106,6 +106,8 @@ func Encode(w io.Writer, v any, opts *EncodeOpts) (written int64, err error) {
                return IntEncode(w, v)
        case string:
                return StrEncode(w, v)
+       case Magic:
+               return MagicEncode(w, v)
        }
        vv := reflect.ValueOf(v)
        switch reflect.TypeOf(v).Kind() {
index 477613869c2ddd250ce0d50181f8dcacf77c8a46e1ccc6413d15db11cc69171a..d7cd6a1bbb44bcc526c557a2ddfad83acfc896bc5e1e772d46a132640d271e39 100644 (file)
@@ -77,7 +77,7 @@ func (iter *Iterator) Next() bool {
                iter.tai64ns++
        case types.TAI64NA:
                iter.tai64nas++
-       case types.Bin, types.Str:
+       case types.Bin, types.Str, types.Magic:
                iter.strs++
        case types.Raw:
                iter.raws++
@@ -138,6 +138,10 @@ func (iter *Iterator) TAI64NA() *tai64n.TAI64NA {
        return &iter.ctx.tai64nas[iter.tai64nas]
 }
 
+func (iter *Iterator) Magic() Magic {
+       return Magic(iter.ctx.strs[iter.strs])
+}
+
 func (iter *Iterator) Bin() []byte {
        return []byte(iter.ctx.strs[iter.strs])
 }
diff --git a/go/magic.go b/go/magic.go
new file mode 100644 (file)
index 0000000..3016347
--- /dev/null
@@ -0,0 +1,42 @@
+// GoKEKS -- Go KEKS codec implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, version 3 of the License.
+//
+// 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package keks
+
+import "go.cypherpunks.su/keks/types"
+
+// Magic string. Up to 12 bytes.
+type Magic string
+
+var MagicDecodeOpts = DecodeOpts{
+       MaxStrLen:        16,
+       MaxContLen:       0,
+       DisableUTF8Check: true,
+       LeaveTAI64:       true,
+}
+
+// Strip off possible Magic atom at the beginning. If not Magic is
+// present, then return empty Magic and data unchanged.
+func StripMagic(data []byte) (Magic, []byte) {
+       d := NewDecoderFromBytes(data, &MagicDecodeOpts)
+       t, err := d.DecodeAtom()
+       if err != nil {
+               return "", data
+       }
+       if t != types.Magic {
+               return "", data
+       }
+       return d.Iter().Magic(), data[d.Read:]
+}
index d0abc7d4905a2e5857cd26648f790baaac846e7356f3bc0a584dfa6dd3742ffc..bced6afd3459ee94c207452b031b40397dff1a42549f4fb2528728c9e2f5fc08 100644 (file)
@@ -14,4 +14,8 @@ const (
        SNTRUP4591761X25519HKDFBLAKE2b = sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b
        BalloonBLAKE2bHKDF             = "balloon-blake2b-hkdf"
        ChaCha20Poly1305               = "chacha20poly1305"
+
+       EncryptedMagic = "pki/encryptd"
+       HashedMagic    = "pki/hashed"
+       PrvKeyMagic    = "pki/prvkey"
 )
index 528aac58e724e90b408adb31a4f75707d907d27fcbfbac16b39e7b35dd90e5a3..1f99071f135af286b7b1837a33a12697210515cc7fbb17865929d6582049959e 100644 (file)
@@ -29,9 +29,10 @@ import (
 )
 
 const (
-       KUCA  = "ca"  // CA-capable key usage
-       KUSig = "sig" // Signing-capable key usage
-       KUKEM = "kem" // Key-encapsulation-mechanism key usage
+       KUCA     = "ca"  // CA-capable key usage
+       KUSig    = "sig" // Signing-capable key usage
+       KUKEM    = "kem" // Key-encapsulation-mechanism key usage
+       CerMagic = "pki/cer"
 )
 
 // Public key.
@@ -49,16 +50,16 @@ type CerLoad struct {
        Pub  []Pub                 `keks:"pub"`
 }
 
-// Parse SignedData contents as CerLoad (certificate) and check its
+// Parse Signed contents as CerLoad (certificate) and check its
 // signatures necessary structure. sd.Load.V will hold the CerLoad in
 // case of success.
-func (sd *SignedData) CerParse() error {
+func (sd *Signed) CerParse() error {
        if sd.Load.T != "cer" {
                return errors.New("CerParse: wrong load type")
        }
        for _, sig := range sd.Sigs {
                if sig.TBS.Hashes != nil {
-                       return errors.New("CerParse: prehashed SignedData")
+                       return errors.New("CerParse: prehashed Signed")
                }
                if sig.TBS.CID == nil {
                        return errors.New("CerParse: missing cid")
@@ -108,9 +109,17 @@ func (sd *SignedData) CerParse() error {
        return nil
 }
 
-// Parse KEKS-encoded data as SignedData with the CerLoad (certificate) contents.
-func CerParse(data []byte) (sd *SignedData, err error) {
-       sd, err = SignedDataParse(data)
+// Parse KEKS-encoded data as Signed with the CerLoad (certificate) contents.
+func CerParse(data []byte) (sd *Signed, err error) {
+       {
+               var magic keks.Magic
+               magic, data = keks.StripMagic(data)
+               if magic != "" && magic != CerMagic {
+                       err = errors.New("wrong magic")
+                       return
+               }
+       }
+       sd, err = SignedParse(data)
        if err != nil {
                return
        }
@@ -127,10 +136,10 @@ func (cer *CerLoad) Can(ku string) (yes bool) {
        return
 }
 
-// Sign the current SignedData, having CerLoad payload with the provided
+// Sign the current Signed, having CerLoad payload with the provided
 // parent's CerLoad and prv key. Certificate's CID will be automatically
 // generated UUIDv7. since and till times must not have nanoseconds part.
-func (sd *SignedData) CerIssueWith(
+func (sd *Signed) CerIssueWith(
        parent *CerLoad,
        prv crypto.Signer,
        since, till time.Time,
@@ -171,9 +180,9 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
        return
 }
 
-// Verify SignedData CerLoad certificate's signature with provided parent.
+// Verify Signed CerLoad certificate's signature with provided parent.
 // Currently only single signature can be verified.
-func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) {
+func (sd *Signed) CerCheckSignatureFrom(parent *CerLoad) (err error) {
        if len(sd.Sigs) != 1 {
                err = errors.New("can verify only single signature")
                return
@@ -187,7 +196,7 @@ func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) {
                err = errors.New("sid != parent pub id")
                return
        }
-       tbs := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
+       tbs := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
        buf, err := keks.EncodeBuf(tbs, nil)
        if err != nil {
                return
@@ -195,9 +204,9 @@ func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) {
        return parent.CheckSignature(buf, sig.Sign.V)
 }
 
-// Get CerLoad from SignedData.
-// Returns nil if SignedData does not hold it (or it is not yet parsed).
-func (sd *SignedData) CerLoad() *CerLoad {
+// Get CerLoad from Signed.
+// Returns nil if Signed does not hold it (or it is not yet parsed).
+func (sd *Signed) CerLoad() *CerLoad {
        if sd.Load.T != "cer" {
                return nil
        }
@@ -208,9 +217,9 @@ func (sd *SignedData) CerLoad() *CerLoad {
        return nil
 }
 
-// Verify sd SignedData CerLoad certificate against cers chain of
+// Verify sd Signed CerLoad certificate against cers chain of
 // certificate authority ones at specified point of time t.
-func (sd *SignedData) CerVerify(cers []*SignedData, t time.Time) (err error) {
+func (sd *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
        {
                exp := *(sd.Sigs[0].TBS.Exp)
                if t.Before(exp[0]) || t.Equal(exp[0]) {
@@ -226,7 +235,7 @@ func (sd *SignedData) CerVerify(cers []*SignedData, t time.Time) (err error) {
        if sid == sd.CerLoad().Pub[0].Id {
                return sd.CerCheckSignatureFrom(sd.CerLoad())
        }
-       idToCer := make(map[uuid.UUID]*SignedData, len(cers))
+       idToCer := make(map[uuid.UUID]*Signed, len(cers))
        for _, cer := range cers {
                cerLoad := cer.CerLoad()
                if !cerLoad.Can(KUSig) || len(cerLoad.Pub) != 1 {
index 039bd6da5f145365fe66b18063cde8ed4fc07a2bb9acdb0864e92217d28a6b5c..abe3ed4945373590bd6f05aedb67fc9a91477f8507cb3f2dd8d935c235d61d97 100755 (executable)
@@ -9,14 +9,21 @@ echo "gost3410-512C gost3410-256A
 ed25519-blake2b ed25519-blake2b" | while read caAlgo eeAlgo ; do
 
 subj="-subj CN=CA -subj C=RU"
-test_expect_success "$caAlgo: CA generation" "certool \
+test_expect_success "$caAlgo: CA load generation" "certool \
     -algo $caAlgo \
     -ku ca -ku sig $subj \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer"
+test_expect_success "$caAlgo: CA generation" "certool \
+    -algo $caAlgo \
+    -ku ca -ku sig $subj \
+    -reuse-key \
+    -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
+    -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer"
 test_expect_success "$caAlgo: CA regeneration" "certool \
     -algo $caAlgo \
     -ku ca -ku sig $subj \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
+    -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer \
     -reuse-key"
 test_expect_success "$caAlgo: CA self-signature" "certool \
     -ca-cer $TMPDIR/ca.cer \
index b767e014b84d16767c9567c8bbcb3cb57e96e201b4cb5e6ab56691770afd0c5b..7b40f29c8108fc104f20d24277b21546627386033b9e51edef144317d129748a 100644 (file)
@@ -16,6 +16,7 @@
 package main
 
 import (
+       "bytes"
        "crypto"
        "errors"
        "flag"
@@ -76,7 +77,6 @@ func main() {
        prvPath := flag.String("prv", "", "Path to private key file")
        cerPath := flag.String("cer", "", "Path to certificate file")
        verify := flag.Bool("verify", false, "Verify provided -cer with -ca-cer")
-       onlyLoad := flag.Bool("only-load", false, "Store only cer-load in -cer")
 
        flag.Parse()
        log.SetFlags(log.Lshortfile)
@@ -102,9 +102,9 @@ func main() {
        till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
 
        var caPrv crypto.Signer
-       var caCers []*pki.SignedData
+       var caCers []*pki.Signed
        for _, issuingCer := range issuingCers {
-               var sd *pki.SignedData
+               var sd *pki.Signed
                sd, err = pki.CerParse(utils.MustReadFile(issuingCer))
                if err != nil {
                        log.Fatal(err)
@@ -122,7 +122,7 @@ func main() {
        }
 
        if *verify {
-               var sd *pki.SignedData
+               var sd *pki.Signed
                sd, err = pki.CerParse(utils.MustReadFile(*cerPath))
                if err != nil {
                        log.Fatal(err)
@@ -138,20 +138,19 @@ func main() {
                log.Fatal("no -prv is set")
        }
 
-       var prv crypto.Signer
        var prvRaw []byte
        var pub []byte
        if *reuseKey {
-               prv, pub, err = pki.PrvParse(utils.MustReadFile(*prvPath))
+               _, pub, err = pki.PrvParse(utils.MustReadFile(*prvPath))
                if err != nil {
                        log.Fatal(err)
                }
        } else {
                switch *algo {
                case pki.Ed25519BLAKE2b:
-                       prv, prvRaw, pub, err = ed25519blake2b.NewKeypair()
+                       _, prvRaw, pub, err = ed25519blake2b.NewKeypair()
                case pki.GOST3410256A, pki.GOST3410512C:
-                       prv, prvRaw, pub, err = gost.NewKeypair(*algo)
+                       _, prvRaw, pub, err = gost.NewKeypair(*algo)
                case pki.SNTRUP4591761X25519:
                        prvRaw, pub, err = sntrup4591761x25519.NewKeypair()
                default:
@@ -160,14 +159,18 @@ func main() {
                if err != nil {
                        log.Fatal(err)
                }
-               var data []byte
-               data, err = keks.EncodeBuf(pki.AV{A: *algo, V: prvRaw}, nil)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               err = os.WriteFile(*prvPath, data, 0o600)
-               if err != nil {
-                       log.Fatal(err)
+               {
+                       var buf bytes.Buffer
+                       if _, err = keks.Encode(&buf, keks.Magic(pki.PrvKeyMagic), nil); err != nil {
+                               log.Fatal(err)
+                       }
+                       if _, err = keks.Encode(&buf, pki.AV{A: *algo, V: prvRaw}, nil); err != nil {
+                               log.Fatal(err)
+                       }
+                       err = os.WriteFile(*prvPath, buf.Bytes(), 0o600)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
                }
        }
 
@@ -180,34 +183,29 @@ func main() {
        if len(ku) > 0 {
                cerLoad.KU = &ku
        }
-       var data []byte
-       if *onlyLoad {
-               if data, err = keks.EncodeBuf(cerLoad, nil); err != nil {
-                       log.Fatal(err)
-               }
-               if err = os.WriteFile(*cerPath, data, 0o666); err != nil {
-                       log.Fatal(err)
-               }
-               return
-       }
        var caCerLoad *pki.CerLoad
        if caPrv == nil {
-               caPrv = prv
                caCerLoad = &cerLoad
        } else {
                caCerLoad = caCers[0].CerLoad()
        }
-       sd := pki.SignedData{Load: pki.SignedDataLoad{T: "cer", V: cerLoad}}
-       if prv != nil {
+       sd := pki.Signed{Load: pki.SignedLoad{T: "cer", V: cerLoad}}
+       if caPrv != nil {
                if err = sd.CerIssueWith(caCerLoad, caPrv, since, till); err != nil {
                        log.Fatal(err)
                }
        }
 
-       if data, err = keks.EncodeBuf(sd, nil); err != nil {
-               log.Fatal(err)
-       }
-       if err = os.WriteFile(*cerPath, data, 0o666); err != nil {
-               log.Fatal(err)
+       {
+               var buf bytes.Buffer
+               if _, err = keks.Encode(&buf, keks.Magic(pki.CerMagic), nil); err != nil {
+                       log.Fatal(err)
+               }
+               if _, err = keks.Encode(&buf, sd, nil); err != nil {
+                       log.Fatal(err)
+               }
+               if err = os.WriteFile(*cerPath, buf.Bytes(), 0o666); err != nil {
+                       log.Fatal(err)
+               }
        }
 }
index 6366cc9e366ab68e0fcfd2c20a8dc4a9f653e1a650966d087f75c053e2be3455..7c745abca8d7a6c1a02d6bd0b4ab7e82677b6b2d702c9e0b2f7ca7d951320120 100644 (file)
@@ -1,4 +1,4 @@
-// enctool -- dealing with KEKS-encoded encrypted-data utility
+// enctool -- dealing with KEKS-encoded pki-encrypted utility
 // Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
 //
 // This program is free software: you can redistribute it and/or modify
index 4d4d2034f30f10ade8820a97aae260cddb223280f1988df93f443e34d018a3f4..d15b15876af4fc6f013345b9cd7694e71316f10227f9c1aaf934a2dd31d820dc 100644 (file)
@@ -1,4 +1,4 @@
-// enctool -- dealing with KEKS-encoded encrypted-data utility
+// enctool -- dealing with KEKS-encoded pki-encrypted utility
 // Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
 //
 // This program is free software: you can redistribute it and/or modify
@@ -36,12 +36,13 @@ import (
        "go.cypherpunks.su/keks"
        "go.cypherpunks.su/keks/pki"
        "go.cypherpunks.su/keks/pki/utils"
+       "go.cypherpunks.su/keks/types"
 )
 
 const (
        BalloonSaltLen          = 8
-       BalloonHKDFSalt         = "keks/enveloped-data/balloon-blake2b-hkdf"
-       SNTRUP4591761X25519Salt = "keks/enveloped-data/sntrup4591761-x25519-hkdf-blake2b"
+       BalloonHKDFSalt         = "keks/pki/encrypted/balloon-blake2b-hkdf"
+       SNTRUP4591761X25519Salt = "keks/pki/encrypted/sntrup4591761-x25519-hkdf-blake2b"
 )
 
 type BalloonCost struct {
@@ -67,7 +68,7 @@ type DEM struct {
        A string `keks:"a"`
 }
 
-type Envelope struct {
+type Encrypted struct {
        DEM  DEM       `keks:"dem"`
        KEM  []KEM     `keks:"kem"`
        Bind uuid.UUID `keks:"bind"`
@@ -130,24 +131,38 @@ func main() {
        var err error
        var cek []byte
        if *doDecrypt {
-               var envelope Envelope
+               {
+                       d := keks.NewDecoderFromReader(os.Stdin, &keks.MagicDecodeOpts)
+                       var t types.Type
+                       t, err = d.DecodeAtom()
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       if t != types.Magic {
+                               log.Fatal("no magic met")
+                       }
+                       if d.Iter().Magic() != pki.EncryptedMagic {
+                               log.Fatal("wrong magic")
+                       }
+               }
+               var encrypted Encrypted
                {
                        d := keks.NewDecoderFromReader(os.Stdin, nil)
-                       err = d.DecodeStruct(&envelope)
+                       err = d.DecodeStruct(&encrypted)
                        if err != nil {
                                log.Fatal(err)
                        }
                }
-               if envelope.Bind == uuid.Nil {
+               if encrypted.Bind == uuid.Nil {
                        log.Fatalln("unll bind")
                }
-               if envelope.DEM.A != pki.ChaCha20Poly1305 {
-                       log.Fatalln("unsupported DEM:", envelope.DEM.A)
+               if encrypted.DEM.A != pki.ChaCha20Poly1305 {
+                       log.Fatalln("unsupported DEM:", encrypted.DEM.A)
                }
-               if len(envelope.KEM) == 0 {
+               if len(encrypted.KEM) == 0 {
                        log.Fatalln("no KEMs")
                }
-               for kemIdx, kem := range envelope.KEM {
+               for kemIdx, kem := range encrypted.KEM {
                        switch kem.A {
                        case pki.BalloonBLAKE2bHKDF:
                                if *passwd == "" {
@@ -163,7 +178,7 @@ func main() {
                                {
                                        kek := hkdf.Extract(blake2b256, balloon.H(blake2b256,
                                                []byte(*passwd),
-                                               append(envelope.Bind[:], *kem.Salt...),
+                                               append(encrypted.Bind[:], *kem.Salt...),
                                                int(kem.Cost.S), int(kem.Cost.T), int(kem.Cost.P),
                                        ), []byte(BalloonHKDFSalt))
                                        var cekp []byte
@@ -220,7 +235,7 @@ func main() {
                                        }
                                        {
                                                ikm := bytes.Join([][]byte{
-                                                       envelope.Bind[:],
+                                                       encrypted.Bind[:],
                                                        *kem.Encap, pubs[prvIdx].V,
                                                        keySNTRUP[:], keyX25519,
                                                }, []byte{})
@@ -356,16 +371,18 @@ func main() {
                        log.Fatal("no KEMs specified")
                }
                {
-                       var hdr []byte
-                       hdr, err = keks.EncodeBuf(&Envelope{
+                       var hdr bytes.Buffer
+                       if _, err = keks.Encode(&hdr, keks.Magic(pki.EncryptedMagic), nil); err != nil {
+                               log.Fatal(err)
+                       }
+                       if _, err = keks.Encode(&hdr, &Encrypted{
                                Bind: binding,
                                KEM:  kems,
                                DEM:  DEM{A: pki.ChaCha20Poly1305},
-                       }, nil)
-                       if err != nil {
+                       }, nil); err != nil {
                                log.Fatal(err)
                        }
-                       if _, err = io.Copy(os.Stdout, bytes.NewReader(hdr)); err != nil {
+                       if _, err = io.Copy(os.Stdout, &hdr); err != nil {
                                log.Fatal(err)
                        }
                }
index 685cd0d9a9f361236e950853f6b19806f56c932135111beffaccdf77b8da1f14..93cdf5f2c690faa68fae74cf2cce861f6946c4f04a56ed54e2fb2e38111d3b3b 100644 (file)
@@ -1,4 +1,4 @@
-// sigtool -- dealing with KEKS-encoded signed-data utility
+// sigtool -- dealing with KEKS-encoded pki-signed utility
 // Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
 //
 // This program is free software: you can redistribute it and/or modify
@@ -36,19 +36,19 @@ import (
 func main() {
        prvPath := flag.String("prv", "", "Path to private key file")
        cerPath := flag.String("cer", "", "Path to certificate file")
-       sdPath := flag.String("sd", "", "Path to signed-data file")
+       sdPath := flag.String("sd", "", "Path to pki-signed file")
        typ := flag.String("type", "data", "Type of the content, /load/t value")
        hashAlgo := flag.String("hash", "", "Algorithm identifier of the hash to use")
        verify := flag.Bool("verify", false, "Verify with provided -cer")
-       envelopeBindingHex := flag.String("envelope-binding", "", "Set envelope-binding")
+       encryptedBindingHex := flag.String("encrypted-binding", "", "Set encrypted-binding")
 
        flag.Parse()
        log.SetFlags(log.Lshortfile)
 
-       envelopeBinding := uuid.Nil
+       encryptedBinding := uuid.Nil
        var err error
-       if *envelopeBindingHex != "" {
-               envelopeBinding, err = uuid.Parse(*envelopeBindingHex)
+       if *encryptedBindingHex != "" {
+               encryptedBinding, err = uuid.Parse(*encryptedBindingHex)
                if err != nil {
                        log.Fatal(err)
                }
@@ -82,8 +82,8 @@ func main() {
                log.Fatal(err)
        }
        if *verify {
-               var sd *pki.SignedData
-               sd, err = pki.SignedDataParse(utils.MustReadFile(*sdPath))
+               var sd *pki.Signed
+               sd, err = pki.SignedParse(utils.MustReadFile(*sdPath))
                if err != nil {
                        log.Fatal(err)
                }
@@ -112,7 +112,7 @@ func main() {
                        log.Fatal(err)
                }
        } else {
-               var sd pki.SignedData
+               var sd pki.Signed
                sd.Load.T = *typ
                sdHashes := map[string]*struct{}{*hashAlgo: nil}
                sd.Hashes = &sdHashes
@@ -122,8 +122,8 @@ func main() {
                        Hashes: &sigHashes,
                        When:   &when,
                }
-               if envelopeBinding != uuid.Nil {
-                       sigTbs.EnvelopeBinding = &envelopeBinding
+               if encryptedBinding != uuid.Nil {
+                       sigTbs.EncryptedBinding = &encryptedBinding
                }
                err = sd.SignWith(cer.CerLoad(), signer, sigTbs)
                if err != nil {
index 6bead4191cb9522e739d119e3058de790b118beb9c9e135e8032499df081d993..fd8f885070c0b2765059180e3bc0916c64d811e984f99ab1fc37159fd4f2d7d4 100644 (file)
@@ -27,10 +27,17 @@ import (
 
 // Parse private key contained in AV KEKS-encoded structure.
 func PrvParse(data []byte) (prv crypto.Signer, pub []byte, err error) {
-       var av AV
+       {
+               var magic keks.Magic
+               magic, data = keks.StripMagic(data)
+               if magic != "" && magic != PrvKeyMagic {
+                       err = errors.New("wrong magic")
+                       return
+               }
+       }
        d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
-       err = d.DecodeStruct(&av)
-       if err != nil {
+       var av AV
+       if err = d.DecodeStruct(&av); err != nil {
                return
        }
        if len(d.B) != 0 {
similarity index 70%
rename from go/pki/signed-data.go
rename to go/pki/signed.go
index 7d9b949eef2199e578d580cd2be939dce9f52af1710c13af637ad5da7320577c..4de2cd85eb99b52704bd4e2de347ae1ddd7f49924579f88378e85737001d10b7 100644 (file)
@@ -26,7 +26,9 @@ import (
        "go.cypherpunks.su/keks"
 )
 
-type SignedDataLoad struct {
+const SignedMagic = "pki/signed"
+
+type SignedLoad struct {
        V any    `keks:"v,omitempty"`
        T string `keks:"t"`
 }
@@ -38,7 +40,7 @@ type SigTBS struct {
        When   *time.Time         `keks:"when,omitempty"`
        SID    uuid.UUID          `keks:"sid"`
 
-       EnvelopeBinding *uuid.UUID `keks:"envelope-binding,omitempty"`
+       EncryptedBinding *uuid.UUID `keks:"encrypted-binding,omitempty"`
 }
 
 type Sig struct {
@@ -47,28 +49,28 @@ type Sig struct {
        Sign   AV        `keks:"sign"`
 }
 
-type SignedDataTBS struct {
+type SignedTBS struct {
        V   any    `keks:"v"`
        T   string `keks:"t"`
        TBS SigTBS `keks:"tbs"`
 }
 
-type SignedData struct {
+type Signed struct {
        Hashes *map[string]*struct{} `keks:"hash,omitempty"`
-       Cers   *[]*SignedData        `keks:"certs,omitempty"`
-       Load   SignedDataLoad        `keks:"load"`
+       Cers   *[]*Signed            `keks:"certs,omitempty"`
+       Load   SignedLoad            `keks:"load"`
        Sigs   []*Sig                `keks:"sigs"`
 }
 
-// Validate parsed signed-data structure.
-func SignedDataValidate(sd *SignedData) (err error) {
+// Validate parsed pki-signed structure.
+func SignedValidate(sd *Signed) (err error) {
        if sd.Hashes != nil && len(*sd.Hashes) == 0 {
-               err = errors.New("SignedDataParse: empty /hash")
+               err = errors.New("SignedParse: empty /hash")
                return
        }
        if sd.Cers != nil {
                if len(*sd.Cers) == 0 {
-                       err = errors.New("SignedDataParse: empty /certs")
+                       err = errors.New("SignedParse: empty /certs")
                        return
                }
                for _, cer := range *sd.Cers {
@@ -80,28 +82,28 @@ func SignedDataValidate(sd *SignedData) (err error) {
        }
        for _, sig := range sd.Sigs {
                if sig.CerLoc != nil && len(*sig.CerLoc) == 0 {
-                       err = errors.New("SignedDataParse: empty cer-loc")
+                       err = errors.New("SignedParse: empty cer-loc")
                        return
                }
                if sig.TBS.Hashes != nil && len(*sig.TBS.Hashes) == 0 {
-                       err = errors.New("SignedDataParse: empty hash")
+                       err = errors.New("SignedParse: empty hash")
                        return
                }
                if sig.TBS.Exp != nil {
                        if len(*sig.TBS.Exp) != 2 {
-                               err = errors.New("SignedDataParse: wrong exp len")
+                               err = errors.New("SignedParse: wrong exp len")
                                return
                        }
                        for _, t := range *sig.TBS.Exp {
                                if t.Nanosecond() != 0 {
-                                       err = errors.New("SignedDataParse: exp with nanoseconds")
+                                       err = errors.New("SignedParse: exp with nanoseconds")
                                        return
                                }
                        }
                }
                if sd.Hashes != nil {
                        if sig.TBS.Hashes == nil {
-                               err = errors.New("SignedDataParse: /sigs: no hash")
+                               err = errors.New("SignedParse: /sigs: no hash")
                                return
                        }
                        var exists bool
@@ -112,7 +114,7 @@ func SignedDataValidate(sd *SignedData) (err error) {
                                }
                        }
                        if !exists {
-                               err = errors.New("SignedDataParse: /sigs: no hash")
+                               err = errors.New("SignedParse: /sigs: no hash")
                                return
                        }
                }
@@ -120,23 +122,30 @@ func SignedDataValidate(sd *SignedData) (err error) {
        return
 }
 
-// Parse signed-data from KEKS-encoded data. This is just a wrapper over
-// SignedDataParseItem.
-func SignedDataParse(data []byte) (*SignedData, error) {
+// Parse pki-signed from KEKS-encoded data. This is just a wrapper over
+// SignedParseItem.
+func SignedParse(data []byte) (*Signed, error) {
+       {
+               var magic keks.Magic
+               magic, data = keks.StripMagic(data)
+               if magic != "" && magic != SignedMagic {
+                       return nil, errors.New("wrong magic")
+               }
+       }
        d := keks.NewDecoderFromBytes(data, nil)
-       var sd SignedData
+       var sd Signed
        err := d.DecodeStruct(&sd)
        if err != nil {
                return nil, err
        }
-       err = SignedDataValidate(&sd)
+       err = SignedValidate(&sd)
        return &sd, err
 }
 
-// Sign SignedData's contents and sigTBS corresponding data with the
+// Sign Signed's contents and sigTBS corresponding data with the
 // provided prv signer, having parent certificate. Signature is appended
 // to the sd.Sigs. parent certificate must have "sig" key-usage.
-func (sd *SignedData) SignWith(
+func (sd *Signed) SignWith(
        parent *CerLoad,
        prv crypto.Signer,
        sigTBS SigTBS,
@@ -145,7 +154,7 @@ func (sd *SignedData) SignWith(
                return errors.New("parent can not sign")
        }
        sigTBS.SID = parent.Pub[0].Id
-       sdTBS := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sigTBS}
+       sdTBS := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sigTBS}
        sig := Sig{TBS: sigTBS}
        sig.Sign.A = parent.Pub[0].A
        var buf []byte
index f094e08ae1d3afec76fe6268c853bb65f03742bf30326a4fa4c07bf4fdaeb8b8..26bee98174807ccd55988c82002d5aa336897db04ddc6b3557e46fb470475d8f 100644 (file)
@@ -22,6 +22,7 @@ const (
        AtomTAI64    AtomType = 0x18
        AtomTAI64N   AtomType = 0x19
        AtomTAI64NA  AtomType = 0x1A
+       AtomMagic    AtomType = 0x4B
 
        AtomStrings = 0x80
        AtomIsUTF8  = 0x40
index e3330440ab343bba24799be1456a7f8d9e99a8d4393f7a509cccc4b27d0c8d96..6fd03eb395532ebca4d86d8c616b311e5251fdbf38e9d755fca43a7e493224ff 100644 (file)
@@ -19,6 +19,7 @@ const (
        TAI64
        TAI64N
        TAI64NA
+       Magic
        Bin
        Str
        Raw
index a1ed2f9b0529d13c2c9bc9b2f5017071ff3b578d19c5bf43f3c3ecd6bf31fcc9..ab7ab4d7e497139b534714ad61c8be1d6dc939bbedacff441bd165b19d984c74 100644 (file)
@@ -23,14 +23,15 @@ func _() {
        _ = x[TAI64-12]
        _ = x[TAI64N-13]
        _ = x[TAI64NA-14]
-       _ = x[Bin-15]
-       _ = x[Str-16]
-       _ = x[Raw-17]
+       _ = x[Magic-15]
+       _ = x[Bin-16]
+       _ = x[Str-17]
+       _ = x[Raw-18]
 }
 
-const _Type_name = "InvalidEOCNILBoolUUIDUIntIntBigIntListMapBlobFloatTAI64TAI64NTAI64NABinStrRaw"
+const _Type_name = "InvalidEOCNILBoolUUIDUIntIntBigIntListMapBlobFloatTAI64TAI64NTAI64NAMagicBinStrRaw"
 
-var _Type_index = [...]uint8{0, 7, 10, 13, 17, 21, 25, 28, 34, 38, 41, 45, 50, 55, 61, 68, 71, 74, 77}
+var _Type_index = [...]uint8{0, 7, 10, 13, 17, 21, 25, 28, 34, 38, 41, 45, 50, 55, 61, 68, 73, 76, 79, 82}
 
 func (i Type) String() string {
        if i >= Type(len(_Type_index)-1) {
index 08b4943ef7ff771cc51cc9553f2ce7bc03c75e8d49f14eca051f8f681d54a537..9716988055df021c5cccbec9a32b308841f0eada34abb911c386f3aafdda47a7 100644 (file)
@@ -116,6 +116,8 @@ func (ctx *Decoder) unmarshal(iter *Iterator) (v any, err error) {
                return toUTC(t.Time())
        case types.TAI64NA:
                return iter.TAI64NA(), nil
+       case types.Magic:
+               return iter.Magic(), nil
        case types.Bin:
                return iter.Bin(), nil
        case types.Str:
index 973d3a6d65ea88aa72c624e790325e00d971988a17aba82d76910bc36eba55fe..f00262e9e2f0cb1b46c2cd129562940f15bfe30da20db95c6bd81d29029518df 100755 (executable)
@@ -58,6 +58,7 @@ TagFloat256 = 0x14
 TagTAI64 = 0x18
 TagTAI64N = 0x19
 TagTAI64NA = 0x1A
+TagMagic = 0x4B
 TagStr = 0x80
 TagUTF8 = 0x40
 
@@ -78,6 +79,7 @@ TagPIntb = _byte(TagPInt)
 TagNIntb = _byte(TagNInt)
 TagTAI64b = _byte(TagTAI64)
 TagTAI64Nb = _byte(TagTAI64N)
+TagMagicb = _byte(TagMagic)
 
 
 class DecodeError(ValueError):
@@ -114,6 +116,26 @@ class Raw:
         return "Raw(%s)" % self.v.hex()
 
 
+class Magic:
+    __slots__ = ("v",)
+
+    def __init__(self, v: bytes):
+        if len(v) > 12:
+            raise ValueError("too long")
+        self.v = v
+
+    def __bytes__(self) -> bytes:
+        return self.v
+
+    def __eq__(self, other) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return self.v == other.v
+
+    def __repr__(self) -> str:
+        return "Magic(%r)" % self.v
+
+
 Blob = namedtuple("Blob", ("l", "v"))
 
 
@@ -261,6 +283,8 @@ def dumps(v):
             append(dumps(v[k]))
         append(TagEOCb)
         return b"".join(raws)
+    if isinstance(v, Magic):
+        return b"".join((b"KEKS", v.v, b"\x00" * (12 - len(v.v))))
     raise NotImplementedError("unsupported type")
 
 
@@ -441,6 +465,12 @@ def _loads(v, sets=False, leapsecUTCAllow=False, _allowContainers=True):
             else:
                 raise DecodeError("wrong chunk len")
         return Blob(l, b"".join(raws)), v
+    if b == TagMagic:
+        if len(v) < 16:
+            raise NotEnoughData(16-len(v))
+        if v[:4] != b"KEKS":
+            raise DecodeError("wrong magic")
+        return Magic(v[4:16].rstrip(b"\x00")), v[16:]
     raise DecodeError("unknown tag")
 
 
@@ -476,12 +506,14 @@ if __name__ == "__main__":
     parser.add_argument("file", type=FileType("rb"))
     args = parser.parse_args()
     data = args.file.read()
-    data, tail = loads(
-        data,
-        sets=not args.nosets,
-        leapsecUTCAllow=args.leapsec_utc_allow is True,
-    )
+    obj = Magic(b"")
     from pprint import pprint
-    pprint(data)
-    if tail != b"":
-        print("tail:", tail.hex())
+    while isinstance(obj, Magic):
+        obj, data = loads(
+            data,
+            sets=not args.nosets,
+            leapsecUTCAllow=args.leapsec_utc_allow is True,
+        )
+        pprint(obj)
+    if data != b"":
+        print("tail:", data.hex())
index 60156f98c0ea4540702a6767165db730b752a8cec989200edc23bd5909c0b495..115341d262357a0ff1fbbd9f9801b05a0d80d4f2f70a5184f772bd7693c757d0 100644 (file)
@@ -53,9 +53,11 @@ data["dates"] = [
     keks.Raw(keks._byte(keks.TagTAI64N) + bytes.fromhex("40000000499602F40006F855")),
     keks.Raw(keks._byte(keks.TagTAI64NA) + bytes.fromhex("40000000499602F40006F855075BCD15")),
 ]
-raw = keks.dumps(data)
+raw = keks.dumps(keks.Magic(b"test-vector")) + keks.dumps(data)
 dec, tail = keks.loads(raw)
+assert dec == keks.Magic(b"test-vector")
+dec, tail = keks.loads(tail)
 assert tail == b""
-assert keks.dumps(dec) == raw
+assert keks.dumps(keks.Magic(b"test-vector")) + keks.dumps(dec) ==  raw
 assert dec == data
 print(raw.hex())
index 02b0b9b93eaa17d0db823f592b7939f24bc953612ee9d6e1914f587858a8ce84..56da544989776ea5c967c47ad3b557d3f62effdbcd79fe39b79ed684caf14251 100644 (file)
@@ -31,6 +31,7 @@ from hypothesis.strategies import uuids
 
 from keks import _byte
 from keks import Blob
+from keks import Magic
 from keks import Raw
 from keks import TagTAI64NA
 from keks import TAI64Base
@@ -45,6 +46,7 @@ mapkey_st = text(alphabet=unicode_allowed, min_size=1, max_size=8)
 tai64na_st = tuples(integers(1, 100), integers(1, 100)).map(lambda x: Raw(
     TagTAI64NAb + (TAI64Base + x[0]).to_bytes(8, "big") + (x[1] + 1).to_bytes(8, "big")
 ))
+magic_st = binary(min_size=0, max_size=12).filter(lambda x: b"\x00" not in x).map(Magic)
 any_st = one_of(
     booleans(),
     integers(),
@@ -57,6 +59,7 @@ any_st = one_of(
     datetimes(),
     blobs_st,
     tai64na_st,
+    magic_st,
 )
 everything_st = deferred(
     lambda: any_st |
diff --git a/py3/tests/test_magic.py b/py3/tests/test_magic.py
new file mode 100644 (file)
index 0000000..838798b
--- /dev/null
@@ -0,0 +1,60 @@
+# PyKEKS -- Python KEKS implementation
+# Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, version 3 of the License.
+#
+# 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from unittest import TestCase
+
+from hypothesis import given
+from hypothesis.strategies import integers
+
+from keks import DecodeError
+from keks import dumps
+from keks import loads
+from keks import Magic
+from keks import NotEnoughData
+from tests.strategies import junk_st
+from tests.strategies import magic_st
+
+
+class TestMagic(TestCase):
+    def test_test_vector(self) -> None:
+        self.assertSequenceEqual(
+            dumps(Magic(b"test-vector")),
+            b"KEKS" + b"test-vector" + b"\x00",
+        )
+
+    def test_empty(self) -> None:
+        self.assertSequenceEqual(dumps(Magic(b"")), b"KEKS" + 12 * b"\x00")
+
+    @given(junk_st, magic_st)
+    def test_symmetric(self, junk: bytes, magic: Magic) -> None:
+        got, tail = loads(dumps(magic) + junk)
+        self.assertEqual(got, magic)
+        self.assertSequenceEqual(tail, junk)
+
+    def test_raises_if_too_long(self) -> None:
+        with self.assertRaises(ValueError):
+            Magic(b"i-am-too-long-string")
+
+    @given(integers(min_value=1, max_value=4))
+    def test_not_enough_data(self, striplen: int) -> None:
+        raw = dumps(Magic(b"short-one"))
+        with self.assertRaises(NotEnoughData) as err:
+            loads(raw[:-striplen])
+        self.assertEqual(err.exception.n, striplen)
+
+    def test_bad_magic(self) -> None:
+        with self.assertRaises(DecodeError) as err:
+            loads(b"KEKs" + 12 * b"\x00")
+        self.assertEqual(str(err.exception), "wrong magic")
index 84282bc942989fb1de5c785750fc5fa08e2d1cdcfe8b2caf021e985d8e159988..b648fd4964807c2a3dc8862185b354a3e570578d183aec62f3035cdd7c02c28c 100755 (executable)
@@ -16,6 +16,7 @@
 # * UTC xxxx-xx-xxTxx:xx:xxZ -- TAI64-encoded UTC is expected
 # * UTC xxxx-xx-xxTxx:xx:xx.xxxxxxZ -- TAI64N-encoded UTC is expected
 # * TAI64NA HEX(...) -- external TAI64NA-encoded time
+# * MAGIC HEX(...) -- Magic is expected
 # * BIN HEX(...) -- BIN is expected
 # * STR HEX(...) -- STR is expected
 # * INT DEC(...) -- ±INT is expected
@@ -30,6 +31,7 @@ from datetime import datetime
 from uuid import UUID
 
 from keks import Blob
+from keks import Magic
 from keks import Raw
 from keks import TagTAI64NA
 
@@ -71,6 +73,8 @@ def textdump(v):
         if v[0] != TagTAI64NA:
             raise NotImplementedError("unsupported Raw type")
         print("TAI64NA " + v[1:].hex())
+    elif isinstance(v, Magic):
+        print("MAGIC " + v.v.hex())
     else:
         raise NotImplementedError("unsupported type")
 
index c4d6c6a44e571ee35180531f05b3ba191308c3b35d37247090c21696a3928c31..987c423a54d6ef370678348374a9e5bd199bfe2dc936ece582a7b26d17236a17 100644 (file)
@@ -34,6 +34,8 @@ Possible values for the tag:
 @item 025 @tab 19 @tab @code{00011001} @tab 12 @tab @ref{TAI64, TAI64N}
 @item 026 @tab 1A @tab @code{00011010} @tab 16 @tab @ref{TAI64, TAI64NA}
 @item [...]
+@item 075 @tab 4B @tab @code{01001011} @tab 15 @tab @ref{Magic, MAGIC}
+@item [...]
 @item 128 @tab 80 @tab @code{10LLLLLL} @tab 0 @tab @ref{Strings, BIN(len=0)}
 @item [...]
 @item 188 @tab BC @tab @code{10111100} @tab 60 @tab @ref{Strings, BIN(len=60)}
@@ -57,5 +59,6 @@ Possible values for the tag:
 @include encoding/int.texi
 @include encoding/float.texi
 @include encoding/tai64.texi
+@include encoding/magic.texi
 @include encoding/cont.texi
 @include encoding/blob.texi
diff --git a/spec/encoding/magic.texi b/spec/encoding/magic.texi
new file mode 100644 (file)
index 0000000..739b201
--- /dev/null
@@ -0,0 +1,16 @@
+@node Magic
+@cindex MAGIC
+@section Magic
+
+MAGIC is an atom holding magic number/string aimed to identify the data
+following it. It is a concatenation of ASCII("KEKS") and 12-byte magic,
+padded with zeros if necessary.
+It is intended to be prepended to the KEKS-encoded data in files.
+
+@multitable @columnfractions .5 .5
+
+@item MAGIC(pki/cer) @tab      @code{4B454B53 706B692F636572 0000000000}
+@item MAGIC(pki/signed) @tab   @code{4B454B53 706B692F7369676E6564 0000}
+@item MAGIC(pki/encryptd) @tab @code{4B454B53 706B692F656E637279707464}
+
+@end multitable
index aedc196c28b171c20cdb8e85f9874fe47b49f567c1adb6ea8205b94b7c473a43..754fac5ed5664d0ac6ed8fcbfa62fcec437e259ca1d758a49ef36c5ae655c8d1 100644 (file)
@@ -79,7 +79,7 @@
 @item 072 @tab 48 @tab @code{01001000} @tab 0 @tab
 @item 073 @tab 49 @tab @code{01001001} @tab 0 @tab
 @item 074 @tab 4A @tab @code{01001010} @tab 0 @tab
-@item 075 @tab 4B @tab @code{01001011} @tab 0 @tab
+@item 075 @tab 4B @tab @code{01001011} @tab 15 @tab @ref{Magic, MAGIC}
 @item 076 @tab 4C @tab @code{01001100} @tab 0 @tab
 @item 077 @tab 4D @tab @code{01001101} @tab 0 @tab
 @item 078 @tab 4E @tab @code{01001110} @tab 0 @tab
index 4bcfe3563d86f0ff33911923c273c1850c80044e1f89435c2ebb8da48d22f686..7672b06c9195ab1970e64d26e65a0e195d8ece92d45d993a444af5a2f30bf66b 100644 (file)
@@ -2,7 +2,9 @@
 @cindex cer
 @section cer format
 
-Certificate is the @code{@ref{signed-data}} structure.
+Certificate is the @code{@ref{pki-signed}} structure.
+Stored in a file, it should start with "pki/cer" @ref{Magic, magic}.
+
 Its @code{/load/t} equals to @code{cer}.
 @code{/load/v} contains @code{cer-load}:
 
@@ -47,7 +49,7 @@ It @strong{must} be absent if empty. Values are extension specific.
 
 @end table
 
-signed-data's sig-tbs @strong{must} contain additional fields:
+@code{pki-signed}'s @code{sig-tbs} @strong{must} contain additional fields:
 
 @verbatiminclude format/cer-sig-tbs.cddl
 
@@ -92,7 +94,7 @@ Example minimal certificate may look like:
 @subsection cer with GOST R 34.10-2012
 
 Same rules of serialisation must be used as with
-@code{@ref{signed-data-gost3410}}. Public key's
+@code{@ref{pki-signed-gost3410}}. Public key's
 identifier and @code{cid} should be calculated
 using big-endian Streebog-256 hash.
 
@@ -100,7 +102,7 @@ using big-endian Streebog-256 hash.
 @subsection cer with Ed25519-BLAKE2b
 
 Same calculation and serialisation rules must be used as with
-@code{@ref{signed-data-ed25519-blake2b}}.
+@code{@ref{pki-signed-ed25519-blake2b}}.
 Public key's identifier and @code{cid} should be calculated
 using BLAKE2b hash with 128 or 256 bit output length specified.
 
similarity index 98%
rename from spec/format/enveloped-data.cddl
rename to spec/format/encrypted.cddl
index 1eedab6bdf508c0f1ff08b034a15cfd0398a82e0bb13ee2d659eb3b4c8c14bcb..829423c2b3d58c2aea0dd44ec79ab2f7c4c729bd54db644e89ff4e5ea824cb36 100644 (file)
@@ -1,6 +1,6 @@
 ai = text ; algorithm identifier
 
-enveloped-data = {
+pki-encrypted = {
     dem: dem,
     kem: [+ kem],
     bind: uuid,
similarity index 81%
rename from spec/format/enveloped-data.texi
rename to spec/format/encrypted.texi
index 93a28eb19f66420f7ebaf8c149b2ec1aac8ce77973c8b7e0af9ebcb6f4006a25..3c07a6dce46d00a489ad913c087f5ad63b3ef8c14f44f4042484bfa2cf2c9226 100644 (file)
@@ -1,10 +1,10 @@
-@node enveloped-data
-@cindex enveloped-data
-@section enveloped-data format
+@node pki-encrypted
+@cindex pki-encrypted
+@section pki-encrypted format
 
-Enveloped data is an encrypted data.
+Stored in a file, it should start with "pki/encryptd" @ref{Magic, magic}.
 
-@verbatiminclude format/enveloped-data.cddl
+@verbatiminclude format/encrypted.cddl
 
 @code{/ciphertext} contains the ciphertext. It is encrypted with random
 "content encryption key" (CEK) with an algorithm specified in
@@ -14,7 +14,7 @@ initialisation vector.
 
 @code{/ciphertext} is a BLOB, which chunk's length depends on DEM
 algorithm. If it is absent, then ciphertext is provided by other means,
-for example just following the enveloped-data structure.
+for example just following the @code{pki-encrypted} structure.
 
 CEK is encapsulated in @code{/kem/*} entries (key encapsulation
 mechanism), using @code{/kem/*/a} algorithm. @code{/kem/*/cek} field
@@ -26,12 +26,12 @@ signatures at all. Optional @code{/kem/*/to}, public key's identifier,
 may provide a hint for quickly searching for the key on the recipient's
 side.
 
-@code{/bind} value can be used to bind the enveloped
-@ref{signed-data, signed-data} to the envelope.
+@code{/bind} value can be used to bind the encrypted
+@code{@ref{pki-signed, pki-signed}} to the envelope.
 Either UUIDv4 or UUIDv7 are recommended.
 
-@node enveloped-data-chacha20poly1305
-@subsection enveloped-data with ChaCha20-Poly1305 DEM
+@node pki-encrypted-chacha20poly1305
+@subsection pki-encrypted with ChaCha20-Poly1305 DEM
 
 @code{/dem/a} equals to "chacha20poly1305". Data is split on 64 KiB
 chunks which are encrypted the following way:
@@ -49,8 +49,8 @@ Last chunk should be smaller than previous ones, maybe even empty.
 
 @code{/ciphertext}'s chunk length equals to 16+64KiB+16 bytes.
 
-@node enveloped-data-kuznechik-ctracpkm-hmac-hkdf
-@subsection enveloped-data-kuznechik-ctracpkm-hmac-hkdf
+@node pki-encrypted-kuznechik-ctracpkm-hmac-hkdf
+@subsection pki-encrypted-kuznechik-ctracpkm-hmac-hkdf
 
 @code{/dem/a} equals to "kuznechik-ctracpkm-hmac-hkdf".
 @code{/dem/seed} contains 16 bytes for the HKDF invocation below.
@@ -58,7 +58,7 @@ Last chunk should be smaller than previous ones, maybe even empty.
 
 @verbatim
 Kenc, Kauth = HKDF-Extract(Streebog-512,
-    salt="keks/enveloped-data/kuznechik-ctracpkm-hmac-hkdf",
+    salt="keks/pki/encrypted/kuznechik-ctracpkm-hmac-hkdf",
     secret=seed || CEK)
 @end verbatim
 
@@ -69,8 +69,8 @@ size. Authentication of ciphertext is performed with Streebog-512 (ГОСТ
 
 @code{/ciphertext}'s chunk length equals to 64KiB bytes.
 
-@node enveloped-data-balloon-blake2b-hkdf
-@subsection enveloped-data with Balloon-BLAKE2b+HKDF-BLAKE2b KEM
+@node pki-encrypted-balloon-blake2b-hkdf
+@subsection pki-encrypted with Balloon-BLAKE2b+HKDF-BLAKE2b KEM
 
 @code{/kem/*/a} equals to "balloon-blake2b-hkdf".
 Recipient map must also contain additional fields:
@@ -93,13 +93,13 @@ password hasher must be used with BLAKE2b-256 hash.
 
 @verbatim
 KEK = HKDF-Extract(BLAKE2b-256,
-    salt="keks/enveloped-data/balloon-blake2b-hkdf",
+    salt="keks/pki/encrypted/balloon-blake2b-hkdf",
     secret=balloon(BLAKE2b-256, password, bind || salt, s, t, p))
 ChaCha20-Poly1305(data=16*0x00 || CEK, key=KEK, nonce=12*0x00, ad="")
 @end verbatim
 
-@node enveloped-data-gost3410-hkdf-kexp15
-@subsection enveloped-data-gost3410-hkdf-kexp15
+@node pki-encrypted-gost3410-hkdf-kexp15
+@subsection pki-encrypted-gost3410-hkdf-kexp15
 
 @code{/kem/*/a} equals to "gost3410-hkdf-kexp15".
 Recipient map must also contain additional fields:
@@ -120,14 +120,14 @@ and KExp15 (Р 1323565.1.017) key wrapping algorithm:
 
 @verbatim
 KEKenv, KEKauth = HKDF-Extract(Streebog-512,
-    salt="keks/enveloped-data/gost3410-hkdf-kexp15",
+    salt="keks/pki/encrypted/gost3410-hkdf-kexp15",
     secret=bind || VKO(...))
 KExp15(KEKenc, KEKauth, IV, CEK):
     return CTR(Kenc, CEK+CMAC(Kauth, IV+CEK), IV=IV)
 @end verbatim
 
-@node enveloped-data-sntrup4591761-x25519-hkdf-blake2b
-@subsection enveloped-data with SNTRUP4591761+x25519+HKDF-BLAKE2b KEM
+@node pki-encrypted-sntrup4591761-x25519-hkdf-blake2b
+@subsection pki-encrypted with SNTRUP4591761+x25519+HKDF-BLAKE2b KEM
 
 @code{/kem/*/a} equals to "sntrup4591761-x25519-hkdf-blake2b".
 Recipient certificate with
@@ -146,7 +146,7 @@ them to get the decryption key of the CEK.
 
 @verbatim
 KEK = HKDF-Extract(BLAKE2b-256,
-    salt="keks/enveloped-data/sntrup4591761-x25519-hkdf-blake2b",
+    salt="keks/pki/encrypted/sntrup4591761-x25519-hkdf-blake2b",
     secret=bind ||
         sntrup4591761-sender-ciphertext ||
         x25519-sender-public-key ||
similarity index 91%
rename from spec/format/hashed-data.cddl
rename to spec/format/hashed.cddl
index 5d0c8b1a879b8813a2d1cd8470a771c4b4de5d3d5d192b495a2fd56552776ebf..39465b0ba3078a8bd789a2f7547109e1f9f944ee913411507dfed047a25dbc37 100644 (file)
@@ -1,6 +1,6 @@
 ai = text ; algorithm identifier
 
-hashed-data = {
+pki-hashed = {
     a: [+ ai],
     t: text, ; type of the content
     v: bytes / text / blob / map / list, ; content itself
similarity index 71%
rename from spec/format/hashed-data.texi
rename to spec/format/hashed.texi
index 9db082da6f917e82ef6f9bf713bd1aa2e7c2b37950ab7f985e35bf53cfd90534..f44efca129a6cf637138c202a8cd7fd9d277658d3ff17a5b97258c7dcd4106fd 100644 (file)
@@ -1,10 +1,11 @@
-@node hashed-data
-@cindex hashed-data
-@section hashed-data format
+@node pki-hashed
+@cindex pki-hashed
+@section pki-hashed format
 
 Integrity protected container, CMS'es DigestedData analogue.
+Stored in a file, it should start with "pki/hashed" @ref{Magic, magic}.
 
-@verbatiminclude format/hashed-data.cddl
+@verbatiminclude format/hashed.cddl
 
 @code{/a} tells what algorithms will be used to hash the data.
 
@@ -18,29 +19,29 @@ converted from BIN to BLOB.
 @code{/hash} contains the hash values for all corresponding @code{/a}
 algorithms.
 
-@node hashed-data-blake2b
-@subsection hashed-data with BLAKE2b
+@node pki-hashed-blake2b
+@subsection pki-hashed with BLAKE2b
 
 @url{https://www.blake2.net/, BLAKE2b} with
 512-bit output has @code{blake2b} algorithm identifier.
 
 256-bit output has @code{blake2b256} algorithm identifier.
 
-@node hashed-data-blake3
-@subsection hashed-data with BLAKE3
+@node pki-hashed-blake3
+@subsection pki-hashed with BLAKE3
 
     @url{https://github.com/BLAKE3-team/BLAKE3/, BLAKE3} with fixed
     256-bit output has @code{blake3} algorithm identifier.
 
-@node hashed-data-sha2
-@subsection hashed-data with SHA2
+@node pki-hashed-sha2
+@subsection pki-hashed with SHA2
 
     SHA2-256 has @code{sha2-256} algorithm identifier.
 
     SHA2-512 has @code{sha2-512} algorithm identifier.
 
-@node hashed-data-shake
-@subsection hashed-data with SHAKE
+@node pki-hashed-shake
+@subsection pki-hashed with SHAKE
 
     @url{https://keccak.team/, SHAKE} XOF function with fixed
     256 (SHAKE128) or 512 (SHAKE256) bit output.
@@ -48,23 +49,23 @@ algorithms.
     Following algorithm identifiers are acceptable:
     @code{shake128}, @code{shake256}.
 
-@node hashed-data-skein512
-@subsection hashed-data with Skein-512
+@node pki-hashed-skein512
+@subsection pki-hashed with Skein-512
 
     512-bit @url{https://www.schneier.com/academic/skein/, Skein-512} hash.
 
     @code{skein512} is acceptable algorithm identifier.
 
-@node hashed-data-gost3411
-@subsection hashed-data with GOST R 34.11-2012
+@node pki-hashed-gost3411
+@subsection pki-hashed with GOST R 34.11-2012
 
     Streebog must be big-endian serialised.
 
     Following algorithm identifiers are acceptable:
     @code{streebog256}, @code{streebog512}.
 
-@node hashed-data-xxh3-128
-@subsection hashed-data with XXH3-128
+@node pki-hashed-xxh3-128
+@subsection pki-hashed with XXH3-128
 
     128-bit @url{https://xxhash.com/, XXH3} hash must be big-endian encoded.
 
index 8941326e0fbf9ebfaf23162ed420c3b9fba58912fb006cc865349293bef19aa7..db281c916f487af04829154d9f5cf675a5bceacdf18cd2f112f7a60d740aa10f 100644 (file)
@@ -6,8 +6,8 @@ They are written in
 @url{https://datatracker.ietf.org/doc/html/rfc8610, CDDL}-like format.
 
 @include format/private-key.texi
-@include format/signed-data.texi
+@include format/signed.texi
 @include format/cer.texi
-@include format/hashed-data.texi
-@include format/enveloped-data.texi
+@include format/hashed.texi
+@include format/encrypted.texi
 @include format/registry.texi
index 52e4850a04f5948af781b72eca10b90b40f742f58da11b68d47780b4557edd70..5b1c0048ab7451eec610af9a676a91a3bd025b2c15a6a864542b01ef2e223b1f 100644 (file)
@@ -6,6 +6,8 @@ Private key is stored in trivial map:
 
 @verbatiminclude format/private-key.cddl
 
+Stored in a file, it should start with "pki/prvkey" @ref{Magic, magic}.
+
 @node private-key-gost3410
 @subsection private-key with GOST R 34.10-2012
 
index f9e614abede3fea707759f2bd65ff319944d6567c971b62b38ac5035df36835d..44391027482615e7577b7d913bef747cc850d3b9c6bf994d1bcc8c39ffe78bcb 100644 (file)
@@ -10,22 +10,22 @@ There is example registry of known algorithm identifiers.
 @table @code
 @item blake2b, blake2b256
     @code{@ref{cer-ed25519-blake2b}},
-    @code{@ref{hashed-data-blake2b}},
-    @code{@ref{signed-data-ed25519-blake2b}}
+    @code{@ref{pki-hashed-blake2b}},
+    @code{@ref{pki-signed-ed25519-blake2b}}
 @item blake3
-    @code{@ref{hashed-data-blake3}}
+    @code{@ref{pki-hashed-blake3}}
 @item sha2-256, sha2-512
-    @code{@ref{hashed-data-sha2}}
+    @code{@ref{pki-hashed-sha2}}
 @item shake128, shake256
-    @code{@ref{hashed-data-shake}}
+    @code{@ref{pki-hashed-shake}}
 @item skein512
-    @code{@ref{hashed-data-skein512}}
+    @code{@ref{pki-hashed-skein512}}
 @item streebog256, streebog512
     @code{@ref{cer-gost3410}},
-    @code{@ref{hashed-data-gost3411}},
-    @code{@ref{signed-data-gost3410}}
+    @code{@ref{pki-hashed-gost3411}},
+    @code{@ref{pki-signed-gost3410}}
 @item xxh3-128
-    @code{@ref{hashed-data-xxh3-128}}
+    @code{@ref{pki-hashed-xxh3-128}}
 @end table
 
 @node AI DH
@@ -45,9 +45,9 @@ There is example registry of known algorithm identifiers.
 
 @table @code
 @item chacha20poly1305
-    @code{@ref{enveloped-data-chacha20poly1305}}
+    @code{@ref{pki-encrypted-chacha20poly1305}}
 @item kuznechik-ctracpkm-hmac-hkdf
-    @code{@ref{enveloped-data-kuznechik-ctracpkm-hmac-hkdf}}
+    @code{@ref{pki-encrypted-kuznechik-ctracpkm-hmac-hkdf}}
 @end table
 
 @node AI KEM
@@ -56,16 +56,16 @@ There is example registry of known algorithm identifiers.
 @table @code
 @item argon2id-hkdf-blake2b
 @item balloon-blake2b-hkdf
-    @code{@ref{enveloped-data-balloon-blake2b-hkdf}}
+    @code{@ref{pki-encrypted-balloon-blake2b-hkdf}}
 @item gost3410-hkdf-kexp15
-    @code{@ref{enveloped-data-gost3410-hkdf-kexp15}}
+    @code{@ref{pki-encrypted-gost3410-hkdf-kexp15}}
 @item mlkem768-x25519
 @item sntrup761-x25519
 @item sntrup4591761-x25519
     @code{@ref{cer-sntrup4591761-x25519}},
     @code{@ref{private-key-sntrup4591761-x25519}}
 @item sntrup4591761-x25519-hkdf-blake2b
-    @code{@ref{enveloped-data-sntrup4591761-x25519-hkdf-blake2b}}
+    @code{@ref{pki-encrypted-sntrup4591761-x25519-hkdf-blake2b}}
 @item sntrup761-x25519-hkdf-blake2b
 @end table
 
@@ -76,13 +76,13 @@ There is example registry of known algorithm identifiers.
 @item ecdsa-nist256p, ecdsa-nist521p
 @item ed25519-blake2b
     @code{@ref{private-key-ed25519-blake2b}}
-    @code{@ref{signed-data-ed25519-blake2b}},
+    @code{@ref{pki-signed-ed25519-blake2b}},
     @code{@ref{cer-ed25519-blake2b}}
 @item ed448
 @item gost3410-256A, gost3410-512C
     @code{@ref{cer-gost3410}},
     @code{@ref{private-key-gost3410}},
-    @code{@ref{signed-data-gost3410}}
+    @code{@ref{pki-signed-gost3410}}
 @end table
 
 @node AI Content types
@@ -90,6 +90,6 @@ There is example registry of known algorithm identifiers.
 
 @itemize
 @item @ref{cer, @code{cer}}
-@item @ref{signed-data, @code{data}}
+@item @ref{pki-signed, @code{data}}
 @item @ref{private-key, @code{prv}}
 @end itemize
similarity index 78%
rename from spec/format/signed-data-tbs.cddl
rename to spec/format/signed-tbs.cddl
index b6f05c2ad0fe701412194fb1ee715aca433d41f8cf4a6713771b6b84c0054e13..137d8a66c8beec6bb7dd04922f899ed80d587444502e80cb8cbf425ed03b4ccc 100644 (file)
@@ -1,4 +1,4 @@
-signed-data-tbs = {
+pki-signed-tbs = {
     t: text, ; = /load/t
     v: nil / any,
     tbs: map, ; = /sigs/?/tbs
similarity index 86%
rename from spec/format/signed-data.cddl
rename to spec/format/signed.cddl
index 4c7a14dacafe1e8124c85744953c0fbc311859f02d7d92daa95c893c873b9d25..6579cb1bf2160a2bba06112cc7de86fbdd13442f4d52c71498abcc85d5fa2fca 100644 (file)
@@ -1,7 +1,7 @@
 ai = text ; algorithm identifier
 av = {a: ai, v: bytes}
 
-signed-data = {
+pki-signed = {
     ? hash: set, ; when using prehashing
     load: {
         t: text,
@@ -11,7 +11,7 @@ signed-data = {
     ? certs: [+ cer],
 }
 
-cer = signed-data ; with /load/t = "cer", /load/v = cer-load
+cer = pki-signed ; with /load/t = "cer", /load/v = cer-load
 
 sig = {
     tbs: sig-tbs,
similarity index 76%
rename from spec/format/signed-data.texi
rename to spec/format/signed.texi
index f5c490ed451b33aba67461c0feed0865373aa21ce0985e8a5ce73a35eba50507..58824076a720c72011a579a233875e0fb93a6dad38763e54f076786f05c161b8 100644 (file)
@@ -1,15 +1,18 @@
-@node signed-data
-@cindex signed-data
-@section signed-data format
+@node pki-signed
+@cindex pki-signed
+@section pki-signed format
 
 That resembles @url{https://datatracker.ietf.org/doc/html/rfc5652, CMS}
 (PKCS#7) ASN.1-based format.
 
-@verbatiminclude format/signed-data.cddl
+Stored in a file, it should start with "pki/signed" @ref{Magic, magic},
+unless this is a @ref{cer, certificate}.
+
+@verbatiminclude format/signed.cddl
 
 Signature is created by signing the following encoded MAP:
 
-@verbatiminclude format/signed-data-tbs.cddl
+@verbatiminclude format/signed-tbs.cddl
 
 If prehashing is used, then @code{/hash} tells what algorithms will be
 used to hash the data of @code{/load/v}. If @code{/load/v} is either a
@@ -30,12 +33,12 @@ are placed outside it.
 help creating the whole verification chain. They are placed outside
 @code{/sigs}, because some of them may be shared among signers.
 
-If signed data is also intended to be @ref{enveloped-data, enveloped}
-(encrypted), then @code{/sigs/*/tbs/envelope-binding} should be set to
-envelop's @code{/bind} value.
+If signed data is also intended to be @ref{pki-encrypted, encrypted},
+then @code{/sigs/*/tbs/encrypted-binding} should be set to
+envelope's @code{/bind} value.
 
-@node signed-data-gost3410
-@subsection signed-data with GOST R 34.10-2012
+@node pki-signed-gost3410
+@subsection pki-signed with GOST R 34.10-2012
 
 GOST R 34.10-2012 must be used with Streebog (GOST R 34.11-2012) hash
 function. Its digest must be big-endian serialised. Public key must be
@@ -48,8 +51,8 @@ Following algorithm identifiers should be used for the hash:
 Following algorithm identifiers are acceptable for the public key and
 signature: @code{gost3410-256A}, @code{gost3410-512C}.
 
-@node signed-data-ed25519-blake2b
-@subsection signed-data with Ed25519-BLAKE2b
+@node pki-signed-ed25519-blake2b
+@subsection pki-signed with Ed25519-BLAKE2b
 
 @url{https://datatracker.ietf.org/doc/html/rfc8032, EdDSA} with
 Edwards25519 is used similarly as in RFC 8032.
index ea9b572f8a2385bfd5e2377548cfdadbb96b066139f2f0cef81352ccbd848642..c21af7c741711f4a1778c6f92a04ec67893ca37f18733d38acd875cf4420ac43 100644 (file)
@@ -31,11 +31,19 @@ proc TRUE {} { char [expr 0x03] }
 
 proc UUID {v} {
     set v [binary decode hex [string map {- ""} $v]]
-    if {[string length $v] != 16} { error "bad UUID len" }
+    if {[string length $v] != 16} { error "bad len" }
     char [expr 0x04]
     add $v
 }
 
+proc MAGIC {v} {
+    set l [string length $v]
+    if {$l > 12} { error "too long" }
+    add "KEKS"
+    add $v
+    add [string repeat [binary format c 0] [expr {12 - $l}]]
+}
+
 proc toBEbin {l v} {
     set a [list]
     for {set i 0} {$i < $l} {incr i} {
@@ -228,7 +236,7 @@ proc RAW {t v} {
     add $v
 }
 
-namespace export EOC NIL FALSE TRUE UUID INT STR BIN RAW
+namespace export EOC NIL FALSE TRUE UUID MAGIC INT STR BIN RAW
 namespace export TAI64 UTCFromISO
 namespace export LIST MAP SET LenFirstSort BLOB
 
index a08bbf68fb34b502b54dc5f87749d23f7cc3dad5427469bb33676ec5e77036c1..c3ad2bfdaf86516c770488616ed70a6da3042bda5b25cf83d96ec663b117a625 100755 (executable)
@@ -31,3 +31,4 @@ dump 'TAI64 -11' >tai-before
 dump 'RAW [expr 0x18] [binary decode hex "40000000586846A4"]' >tai-leap
 dump 'TAI64 1234 1234' >tai-ns
 dump 'TAI64 1234 1234 1234' >tai-as
+dump "MAGIC fuzz" >magic
index f2167bf064d7fbbb118d65ee45cdc0beb0803767684e039233150b4669a9f6b0..74b1281699b351be93522278f8eddf7113e203800a408042869709764743f71c 100644 (file)
@@ -1,6 +1,7 @@
 source keks.tcl
 namespace import KEKS::*
 
+MAGIC test-vector
 MAP {
     ints {MAP {
         pos {LIST {