sntrup4591761x25519 "go.cypherpunks.su/keks/cm/enc/sntrup4591761-x25519"
cmhash "go.cypherpunks.su/keks/cm/hash"
"go.cypherpunks.su/keks/cm/sign"
+ "go.cypherpunks.su/keks/schema"
"go.cypherpunks.su/keks/types"
)
case sign.PrvMagic:
case cmenc.Magic:
var encrypted cmenc.Encrypted
+ var v any
{
d := keks.NewDecoderFromBytes(data, nil)
- err = d.DecodeStruct(&encrypted)
+ v, err = d.Decode()
+ if err != nil {
+ return
+ }
+ err = schema.Check("encrypted", cmenc.EncryptedSchemas, v)
+ if err != nil {
+ return
+ }
+ err = d.UnmarshalStruct(&encrypted)
if err != nil {
return
}
err = errors.New("wrong prv encryption KEM")
return
}
+ v = v.(map[string]any)["kem"].([]any)[0]
+ err = schema.Check("kem-balloon-blake2b-hkdf", cmenc.EncryptedSchemas, v)
+ if err != nil {
+ return
+ }
+
passwd := readPasswd("Passphrase for private key:")
var cek []byte
cek, err = cmballoon.Decapsulate(encrypted.KEM[0], encrypted.Id[:], passwd)
err = errors.New("wrong magic")
return
}
+ sign.PrvParse(data)
d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
err = d.DecodeStruct(&av)
tail = d.B
var encrypted cmenc.Encrypted
{
d := keks.NewDecoderFromReader(os.Stdin, nil)
- err = d.DecodeStruct(&encrypted)
+ var v any
+ v, err = d.Decode()
if err != nil {
log.Fatal(err)
}
+ if err = schema.Check("encrypted", cmenc.EncryptedSchemas, v); err != nil {
+ log.Fatal(err)
+ }
+ for _, kem := range v.(map[string]any)["kem"].([]any) {
+ kemMap := kem.(map[string]any)
+ switch kemMap["a"] {
+ case cmballoon.BalloonBLAKE2bHKDF:
+ err = schema.Check("kem-balloon-blake2b-hkdf", cmenc.EncryptedSchemas, kem)
+ case sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b:
+ fallthrough
+ case mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256:
+ err = schema.Check("kem-with-encap", cmenc.EncryptedSchemas, kem)
+ default:
+ log.Fatal("unsupported KEM algorithm")
+ }
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err = d.UnmarshalStruct(&encrypted); err != nil {
+ log.Fatal(err)
+ }
}
if encrypted.DEM.A != chapoly.DEMAlgo {
log.Fatalln("unsupported DEM:", encrypted.DEM.A)
cmhash "go.cypherpunks.su/keks/cm/hash"
"go.cypherpunks.su/keks/cm/sign"
"go.cypherpunks.su/keks/cm/sign/mode"
+ "go.cypherpunks.su/keks/schema"
"go.cypherpunks.su/keks/types"
)
if t != types.Magic || decoder.Iter().Magic() != sign.SignedMagic {
log.Fatal("wrong magic")
}
- decoder = keks.NewDecoderFromReader(stdin, nil)
- if _, err = decoder.Parse(); err != nil {
+ decoder = keks.NewDecoderFromReader(stdin, &keks.DecodeOpts{LeaveTAI64: true})
+ var v any
+ v, err = decoder.Decode()
+ if err != nil {
log.Fatal(err)
}
var prehash sign.Prehash
+ err = schema.Check("prehash", sign.SignedSchemas, v)
+ if err == nil {
+ err = decoder.UnmarshalStruct(&prehash)
+ }
var signed sign.Signed
- err = decoder.UnmarshalStruct(&prehash)
var hasher hash.Hash
if err == nil && prehash.T == mode.PrehashT {
- if len(prehash.Sigs) == 0 {
- log.Fatal("prehash: no sigs")
- }
if len(prehash.Sigs) > 1 {
log.Fatal("prehash: currently only single signature support")
}
log.Fatal(err)
}
}
- decoder = keks.NewDecoderFromReader(stdin, nil)
- err = decoder.DecodeStruct(&signed)
- } else {
- err = decoder.UnmarshalStruct(&signed)
+ decoder = keks.NewDecoderFromReader(stdin, &keks.DecodeOpts{LeaveTAI64: true})
+ v, err = decoder.Decode()
+ if err != nil {
+ log.Fatal(err)
+ }
}
+ err = schema.Check("signed", sign.SignedSchemas, v)
if err != nil {
log.Fatal(err)
}
- if err = sign.SignedValidate(&signed); err != nil {
+ err = decoder.UnmarshalStruct(&signed)
+ if err != nil {
log.Fatal(err)
}
if len(signed.Sigs) == 0 {
--- /dev/null
+n=${2##*/}.tcl
+redo-ifchange ../../tcl/schema2bin ../../tcl/schemas/$n
+../../tcl/schema2bin ../../tcl/schemas/$n | xxd -r -p
--- /dev/null
+// GoKEKS/CM -- KEKS-encoded cryptographic messages
+// 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 encrypted
+
+import (
+ _ "embed"
+
+ "go.cypherpunks.su/keks"
+ "go.cypherpunks.su/keks/schema"
+)
+
+//go:embed encrypted.schema.keks
+var EncryptedSchemasRaw []byte
+
+var EncryptedSchemas map[string][][]any
+
+func init() {
+ var magic keks.Magic
+ magic, EncryptedSchemasRaw = keks.StripMagic(EncryptedSchemasRaw)
+ if magic != schema.Magic {
+ panic("wrong magic in signed.schema.keks")
+ }
+ if err := keks.NewDecoderFromBytes(
+ EncryptedSchemasRaw, nil,
+ ).DecodeStruct(&EncryptedSchemas); err != nil {
+ panic(err)
+ }
+}
"go.cypherpunks.su/keks/cm"
ed25519blake2b "go.cypherpunks.su/keks/cm/sign/ed25519-blake2b"
"go.cypherpunks.su/keks/cm/sign/gost"
+ "go.cypherpunks.su/keks/schema"
)
const PrvMagic = keks.Magic("cm/prv")
}
}
d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
- var av cm.AV
- if err = d.DecodeStruct(&av); err != nil {
- return
+ {
+ var v any
+ v, err = d.Decode()
+ if err != nil {
+ return
+ }
+ err = schema.Check("av", PubSchemas, v)
+ if err != nil {
+ return
+ }
}
- if len(d.B) != 0 {
- err = errors.New("trailing data")
+ var av cm.AV
+ if err = d.UnmarshalStruct(&av); err != nil {
return
}
switch av.A {
"go.cypherpunks.su/keks/cm"
ed25519blake2b "go.cypherpunks.su/keks/cm/sign/ed25519-blake2b"
"go.cypherpunks.su/keks/cm/sign/gost"
+ "go.cypherpunks.su/keks/schema"
)
const (
KUCA = "ca" // CA-capable key usage
KUSig = "sig" // Signing-capable key usage
KUKEM = "kem" // Key-encapsulation-mechanism key usage
+ FPRLen = 32 // fingerprint's length
PubMagic = keks.Magic("cm/pub")
- FPRLen = 32 // fingerprint's length
)
var (
Id []byte `keks:"id"`
}
-// Parse Signed contents as PubLoad (certificate) and check its
-// signatures necessary structure. signed.Load.V will hold the
-// PubLoad in case of success.
-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
-}
-
// Parse KEKS-encoded data as Signed with the PubLoad (certificate) contents.
func PubParse(data []byte) (signed *Signed, tail []byte, err error) {
{
return
}
}
- signed, tail, err = SignedParse(data)
+ d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{LeaveTAI64: true})
+ {
+ var v any
+ v, err = d.Decode()
+ if err != nil {
+ return
+ }
+ err = schema.Check("pub", PubSchemas, v)
+ if err != nil {
+ return
+ }
+ }
+ d = keks.NewDecoderFromBytes(data, nil)
+ var sd Signed
+ err = d.DecodeStruct(&sd)
if err != nil {
return
}
- err = signed.PubParse()
+ tail = d.B
+ signed = &sd
+ if sd.Load.T != "pub" {
+ err = errors.New("PubParse: wrong load type")
+ }
return
}
--- /dev/null
+// GoKEKS/CM -- KEKS-encoded cryptographic messages
+// 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 sign
+
+import (
+ _ "embed"
+
+ "go.cypherpunks.su/keks"
+ "go.cypherpunks.su/keks/schema"
+)
+
+//go:embed signed.schema.keks
+var SignedSchemasRaw []byte
+
+//go:embed pub.schema.keks
+var PubSchemasRaw []byte
+
+var (
+ SignedSchemas map[string][][]any
+ PubSchemas map[string][][]any
+)
+
+func init() {
+ var magic keks.Magic
+ magic, SignedSchemasRaw = keks.StripMagic(SignedSchemasRaw)
+ if magic != schema.Magic {
+ panic("wrong magic in signed.schema.keks")
+ }
+ if err := keks.NewDecoderFromBytes(
+ SignedSchemasRaw, nil,
+ ).DecodeStruct(&SignedSchemas); err != nil {
+ panic(err)
+ }
+
+ magic, PubSchemasRaw = keks.StripMagic(PubSchemasRaw)
+ if magic != schema.Magic {
+ panic("wrong magic in pub.schema.keks")
+ }
+ if err := keks.NewDecoderFromBytes(
+ PubSchemasRaw, nil,
+ ).DecodeStruct(&PubSchemas); err != nil {
+ panic(err)
+ }
+}
"go.cypherpunks.su/keks"
"go.cypherpunks.su/keks/cm"
"go.cypherpunks.su/keks/cm/sign/mode"
+ "go.cypherpunks.su/keks/schema"
)
const SignedMagic = keks.Magic("cm/signed")
CID uuid.UUID `keks:"cid"`
}
-type Sig struct {
- TBS map[string]any `keks:"tbs"`
- Sign cm.AV `keks:"sign"`
-}
-
func (sig *Sig) TBSGet() (*TBS, error) {
var tbs TBS
return &tbs, keks.Map2Struct(&tbs, sig.TBS)
}
+type Sig struct {
+ TBS map[string]any `keks:"tbs"`
+ Sign cm.AV `keks:"sign"`
+}
+
type Signed struct {
Load Load `keks:"load"`
Pubs *[]*Signed `keks:"pubs,omitempty"`
Sigs []*Sig `keks:"sigs,omitempty"`
}
-// Validate parsed cm-signed structure.
-func SignedValidate(signed *Signed) (err error) {
- if signed.Pubs != nil {
- if len(*signed.Pubs) == 0 {
- err = errors.New("SignedParse: empty /pubs")
+// Parse and validate cm-signed from KEKS-encoded data.
+func Parse(data []byte) (signed *Signed, tail []byte, err error) {
+ {
+ var magic keks.Magic
+ magic, data = keks.StripMagic(data)
+ if magic != "" && magic != SignedMagic {
+ err = errors.New("wrong magic")
return
}
- for _, pub := range *signed.Pubs {
- err = pub.PubParse()
- if err != nil {
- return
- }
- }
}
- for _, sig := range signed.Sigs {
- var tbs *TBS
- tbs, err = sig.TBSGet()
+ d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{LeaveTAI64: true})
+ {
+ var v any
+ v, err = d.Decode()
if err != nil {
return
}
- if tbs.Exp != nil {
- if len(tbs.Exp) != 2 {
- err = errors.New("SignedParse: wrong exp len")
- return
- }
- for _, t := range tbs.Exp {
- if t.Nanosecond() != 0 {
- err = errors.New("SignedParse: exp with nanoseconds")
- return
- }
- }
- }
- }
- return
-}
-
-// Parse cm-signed from KEKS-encoded data. This is just a wrapper over
-// DecodeStruct and SignedValidate.
-func SignedParse(data []byte) (signed *Signed, tail []byte, err error) {
- {
- var magic keks.Magic
- magic, data = keks.StripMagic(data)
- if magic != "" && magic != SignedMagic {
- err = errors.New("wrong magic")
+ err = schema.Check("signed", SignedSchemas, v)
+ if err != nil {
return
}
}
- d := keks.NewDecoderFromBytes(data, nil)
+ d = keks.NewDecoderFromBytes(data, nil)
var sd Signed
err = d.DecodeStruct(&sd)
if err != nil {
}
tail = d.B
signed = &sd
- err = SignedValidate(signed)
return
}
}
kem-mceliece6960119-x25519-hkdf-shake256 = {
- a: "mceliece6960119-x25519-hkdf-shake256 ",
+ a: "mceliece6960119-x25519-hkdf-shake256",
cek: bytes,
encap: bytes,
? to: fpr, ; recipient's public key fingerprint
our {
{HAS a}
{TYPE= a {STR}}
- {TAKE a}
- {GT 0}
+ {!EMPTY a}
{HAS v}
{TYPE= v {BIN STR}}
@item SCHEMA* k s
Check each element of "k" against "s" schema.
+@item !EMPTY k
+Check that "k" element's length is greater than zero.
+
+@item IS-SET k
+Check that "k" is non-empty set (map with NIL values).
+
@end table
}]
}
+proc !EMPTY {k} {
+ evals [subst {
+ {TAKE $k}
+ {GT 0}
+ }]
+}
+
+proc IS-SET {k} {
+ evals [subst {
+ {TAKE $k}
+ {TYPE {MAP}}
+ {TAKE $k}
+ {GT 0}
+ {TAKE $k}
+ {EACH}
+ {TYPE {NIL}}
+ }]
+}
+
MAGIC schema
source [lindex $::argv 0]
puts [binary encode hex $::KEKS::buf]
--- /dev/null
+SCHEMAS {
+
+encrypted {
+ {HAS dem}
+ {HAS kem}
+ {TYPE= id {HEXLET}}
+ {TYPE= payload {BIN}}
+ {TYPE= dem {MAP}}
+ {TYPE= kem {LIST}}
+ {!EMPTY kem}
+ {SCHEMA= dem dem}
+ {SCHEMA* kem kem}
+}
+
+dem {
+ {HAS a}
+ {TYPE= a {STR}}
+ {!EMPTY a}
+}
+
+kem {
+ {HAS a}
+ {TYPE= a {STR}}
+ {!EMPTY a}
+ {HAS cek}
+ {TYPE= cek {BIN}}
+}
+
+balloon-cost {
+ {HAS s}
+ {HAS t}
+ {HAS p}
+ {TYPE= s INT}
+ {TYPE= t INT}
+ {TYPE= p INT}
+}
+
+kem-balloon-blake2b-hkdf {
+ {HAS a}
+ {TYPE= a {STR}}
+ {!EMPTY a}
+ {HAS cek}
+ {TYPE= cek {BIN}}
+ {HAS salt}
+ {TYPE= salt {BIN}}
+ {HAS cost}
+ {SCHEMA= cost balloon-cost}
+}
+
+fpr {
+ {TYPE= . {BIN}}
+ {LEN= . 32}
+}
+
+kem-with-encap {
+ {HAS a}
+ {TYPE= a {STR}}
+ {!EMPTY a}
+ {HAS cek}
+ {TYPE= cek {BIN}}
+ {HAS encap}
+ {TYPE= encap {BIN}}
+ {SCHEMA= to fpr}
+}
+
+}
av {
{HAS a}
{TYPE= a {STR}}
- {TAKE a}
- {GT 0}
-
+ {!EMPTY a}
{HAS v}
{TYPE= v {BIN}}
}
+
pub {
{HAS load}
{SCHEMA= load load}
{SCHEMA* sigs sig}
{TYPE= pubs {LIST}}
- {TAKE pubs}
- {GT 0}
+ {!EMPTY pubs}
{SCHEMA* pubs pub}
}
+
load {
{HAS t}
{TYPE= t {STR}}
- {TAKE t}
- {GT 0}
-
+ {!EMPTY t}
{HAS v}
{SCHEMA= v pub-load}
}
+
sig {
{HAS tbs}
{HAS sign}
- {SCHEMA= sign av}
{SCHEMA= tbs tbs}
+ {SCHEMA= sign av}
+}
+
+exp {
+ {TYPE= . {LIST}}
+ {LEN= . 2}
+ {TYPE* . {TAI64}}
+ {TAKE .}
+ {EACH}
+ {TIMEMAXPREC 0}
}
+
+fpr {
+ {TYPE= . {BIN}}
+ {LEN= . 32}
+}
+
tbs {
{HAS sid}
- {TYPE= sid {BIN}}
- {LEN= sid 32}
+ {SCHEMA= sid fpr}
{HAS cid}
{TYPE= cid {HEXLET}}
{HAS exp}
- {TYPE= exp {LIST}}
- {LEN= exp 2}
- {TYPE* exp {TAI64}}
- {TAKE exp}
- {EACH}
- {TIMEMAXPREC 0}
-
- {TYPE= when {TAI64}}
+ {SCHEMA= exp exp}
{TYPE= nonce {BIN}}
- {TAKE nonce}
- {GT 0}
+ {!EMPTY nonce}
+
+ {TYPE= when {TAI64}}
}
+
pub-load {
{HAS id}
- {TYPE= id {BIN}}
- {LEN= id 32}
+ {SCHEMA= id fpr}
{!HAS crit}
- {TYPE= ku {MAP}}
- {TAKE ku}
- {GT 0}
- {TYPE* ku {NIL}}
+ {IS-SET ku}
{HAS pub}
{TYPE= pub {LIST}}
- {TAKE pub}
- {GT 0}
+ {!EMPTY pub}
{SCHEMA* pub av}
{HAS sub}
- {TAKE sub}
- {TYPE {MAP}}
- {TAKE sub}
- {GT 0}
+ {TYPE= sub {MAP}}
+ {!EMPTY sub}
{TYPE* sub {STR}}
}
--- /dev/null
+SCHEMAS {
+
+prehash {
+ {HAS t}
+ {TYPE= t {STR}}
+ {HAS sigs}
+ {IS-SET sigs}
+}
+
+av {
+ {HAS a}
+ {TYPE= a {STR}}
+ {!EMPTY a}
+ {HAS v}
+ {TYPE= v {BIN}}
+}
+
+signed {
+ {HAS load}
+ {SCHEMA= load load}
+ {TYPE= sigs {LIST}}
+ {SCHEMA* sigs sig}
+
+ {TYPE= pubs {LIST}}
+ {!EMPTY pubs}
+}
+
+load {
+ {HAS t}
+ {TYPE= t {STR}}
+ {!EMPTY t}
+}
+
+sig {
+ {HAS tbs}
+ {HAS sign}
+ {SCHEMA= tbs tbs}
+ {SCHEMA= sign av}
+}
+
+fpr {
+ {TYPE= . {BIN}}
+ {LEN= . 32}
+}
+
+tbs {
+ {HAS sid}
+ {TYPE= sid {BIN}}
+ {LEN= sid 32}
+ {TYPE= nonce {BIN}}
+ {!EMPTY nonce}
+ {TYPE= when {TAI64}}
+
+ {TYPE= encrypted-to {LIST}}
+ {!EMPTY encrypted-to}
+ {SCHEMA* encrypted-to fpr}
+}
+
+}