]> Cypherpunks repositories - keks.git/commitdiff
Revised detached signatures
authorSergey Matveev <stargrave@stargrave.org>
Thu, 23 Jan 2025 08:55:35 +0000 (11:55 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 23 Jan 2025 15:15:06 +0000 (18:15 +0300)
32 files changed:
go/blob.go
go/ctx.go
go/getter.go
go/pki/algo.go
go/pki/cer.go
go/pki/cmd/certool/basic.t
go/pki/cmd/certool/main.go
go/pki/cmd/certool/usage.go [new file with mode: 0644]
go/pki/cmd/enctool/main.go
go/pki/cmd/enctool/usage.go [new file with mode: 0644]
go/pki/cmd/sigtool/main.go
go/pki/cmd/sigtool/usage.go [new file with mode: 0644]
go/pki/ed25519-blake2b/ed25519-to-blake2b.patch
go/pki/ed25519-blake2b/kp.go
go/pki/ed25519-blake2b/mk-from-go
go/pki/ed25519-blake2b/prv.go
go/pki/ed25519-blake2b/verify.go
go/pki/gost/kp.go
go/pki/gost/signer.go
go/pki/gost/verify.go
go/pki/hash/algo.go
go/pki/prv.go
go/pki/signed.go
spec/format/cer-load.cddl
spec/format/cer.texi
spec/format/encrypted.cddl
spec/format/private-key.texi
spec/format/registry.texi
spec/format/signed-prehash.cddl [new file with mode: 0644]
spec/format/signed-tbs.cddl
spec/format/signed.cddl
spec/format/signed.texi

index 0d52c15873e1c9b0332fbe0b729f69c271f8a12b3ffcc5ae9a62d020b497c763..017624b181f072dd48cc806cb62e7c3fd2ea0e30d65c78a931bbeba48c830ba9 100644 (file)
 package keks
 
 import (
+       "errors"
        "fmt"
        "io"
        "strings"
+       "unsafe"
+
+       "go.cypherpunks.su/keks/types"
 )
 
 type BlobChunked struct {
@@ -53,3 +57,59 @@ type BlobReader struct {
 func (blob *BlobReader) String() string {
        return fmt.Sprintf("BLOB(%d, ?)", blob.ChunkLen)
 }
+
+// BlobDecoder decodes a BLOB from the reader.
+type BlobDecoder struct {
+       d        *Decoder
+       ChunkLen int64
+       eof      bool
+}
+
+func NewBlobDecoder(r io.Reader, maxChunkLen int64) (*BlobDecoder, error) {
+       d := NewDecoderFromReader(r, &DecodeOpts{MaxStrLen: maxChunkLen})
+       t, err := d.DecodeAtom()
+       if err != nil {
+               return nil, err
+       }
+       if t != types.Blob {
+               return nil, errors.New("not a BLOB")
+       }
+       chunkLen := d.blobChunkLens[0]
+       if chunkLen > maxChunkLen {
+               return nil, ErrLenTooBig
+       }
+       d.readBuf = make([]byte, max(chunkLen, 8))
+       return &BlobDecoder{d: d, ChunkLen: chunkLen}, nil
+}
+
+// Get the next chunk of the BLOB. Last one may be smaller size than the
+// ChunkLen. When BLOB is finished, io.EOF is returned with nil chunk.
+func (d *BlobDecoder) Next() (chunk []byte, err error) {
+       if d.eof {
+               return nil, io.EOF
+       }
+       var t types.Type
+       t, err = d.d.DecodeAtom()
+       if err != nil {
+               return
+       }
+       if t != types.Bin {
+               err = ErrBlobBadAtom
+               return
+       }
+       s := d.d.strs[len(d.d.strs)-1]
+       d.d.deTail()
+       d.d.strs = d.d.strs[:len(d.d.strs)-1]
+       chunk = unsafe.Slice(unsafe.StringData(s), len(s))
+       if int64(len(s)) == d.ChunkLen {
+               return chunk, nil
+       }
+       if int64(len(s)) > d.ChunkLen {
+               return nil, ErrBlobBadChunkLen
+       }
+       d.eof = true
+       if len(s) == 0 {
+               return nil, io.EOF
+       }
+       return chunk, nil
+}
index 148a7b85a110173438e8fc2864c886a896caf452e987c7ea2e62120eaa0815f7..01e053be49d4015372aa314a250f7c4758f1b9d83d39dcffc53203af2cd326bb 100644 (file)
--- a/go/ctx.go
+++ b/go/ctx.go
@@ -75,6 +75,8 @@ type Decoder struct {
 
        blobChunkLens []int64
        blobChunkses  [][]string
+
+       readBuf []byte // used only by BlobDecoder
 }
 
 // Initialise decoder that will read from b bytes. After the parse,
index f34bf23a6d6c07ec7fc7490c39d6a4ad72aae6f4a128bb6882d0116d9acd7a00..0c196a21dcac5923d6f8a0a0cddd26bb84918e0b89e48e595dbec778135d798e 100644 (file)
@@ -43,7 +43,15 @@ func (ctx *Decoder) getByte() (b byte, err error) {
 func (ctx *Decoder) getBytes(n int) (s string, err error) {
        var read int
        if ctx.B == nil {
-               buf := make([]byte, n)
+               var buf []byte
+               if ctx.readBuf == nil {
+                       buf = make([]byte, n)
+               } else {
+                       buf = ctx.readBuf[:n]
+                       if len(ctx.readBuf) < n {
+                               panic("readBuf is smaller")
+                       }
+               }
                read, err = io.ReadFull(ctx.R, buf)
                ctx.Read += int64(read)
                if err != nil {
index bced6afd3459ee94c207452b031b40397dff1a42549f4fb2528728c9e2f5fc08..695aae9e366a6695015b627f98055dcdd29cc21c51b91c61580141560406da33 100644 (file)
@@ -1,6 +1,7 @@
 package pki
 
 import (
+       "go.cypherpunks.su/keks"
        ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
        "go.cypherpunks.su/keks/pki/gost"
        sntrup4591761x25519 "go.cypherpunks.su/keks/pki/sntrup4591761-x25519"
@@ -15,7 +16,7 @@ const (
        BalloonBLAKE2bHKDF             = "balloon-blake2b-hkdf"
        ChaCha20Poly1305               = "chacha20poly1305"
 
-       EncryptedMagic = "pki/encryptd"
-       HashedMagic    = "pki/hashed"
-       PrvKeyMagic    = "pki/prvkey"
+       EncryptedMagic = keks.Magic("pki/encryptd")
+       HashedMagic    = keks.Magic("pki/hashed")
+       PrvKeyMagic    = keks.Magic("pki/prvkey")
 )
index 1f99071f135af286b7b1837a33a12697210515cc7fbb17865929d6582049959e..e5d38a00e378d67da4253a520fd5857ef682fddf4ce944f53732bb0d93b37a6b 100644 (file)
@@ -19,6 +19,7 @@ import (
        "crypto"
        "errors"
        "fmt"
+       "hash"
        "time"
 
        "github.com/google/uuid"
@@ -32,7 +33,7 @@ const (
        KUCA     = "ca"  // CA-capable key usage
        KUSig    = "sig" // Signing-capable key usage
        KUKEM    = "kem" // Key-encapsulation-mechanism key usage
-       CerMagic = "pki/cer"
+       CerMagic = keks.Magic("pki/cer")
 )
 
 // Public key.
@@ -51,16 +52,13 @@ type CerLoad struct {
 }
 
 // Parse Signed contents as CerLoad (certificate) and check its
-// signatures necessary structure. sd.Load.V will hold the CerLoad in
+// signatures necessary structure. signed.Load.V will hold the CerLoad in
 // case of success.
-func (sd *Signed) CerParse() error {
-       if sd.Load.T != "cer" {
+func (signed *Signed) CerParse() error {
+       if signed.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 Signed")
-               }
+       for _, sig := range signed.Sigs {
                if sig.TBS.CID == nil {
                        return errors.New("CerParse: missing cid")
                }
@@ -68,9 +66,12 @@ func (sd *Signed) CerParse() error {
                        return errors.New("CerParse: missing exp")
                }
        }
+       if signed.Load.V == nil {
+               return errors.New("CerParse: missing /load/v")
+       }
        var load CerLoad
        var err error
-       if v, ok := sd.Load.V.(map[string]any); ok {
+       if v, ok := (*signed.Load.V).(map[string]any); ok {
                err = keks.Map2Struct(&load, v)
        } else {
                err = errors.New("CerParse: wrong /load/v")
@@ -78,7 +79,10 @@ func (sd *Signed) CerParse() error {
        if err != nil {
                return err
        }
-       sd.Load.V = load
+       {
+               loadAny := any(load)
+               signed.Load.V = &loadAny
+       }
        if load.KU != nil {
                if len(*load.KU) == 0 {
                        return errors.New("CerParse: empty ku")
@@ -110,7 +114,7 @@ func (sd *Signed) CerParse() error {
 }
 
 // Parse KEKS-encoded data as Signed with the CerLoad (certificate) contents.
-func CerParse(data []byte) (sd *Signed, err error) {
+func CerParse(data []byte) (signed *Signed, err error) {
        {
                var magic keks.Magic
                magic, data = keks.StripMagic(data)
@@ -119,11 +123,11 @@ func CerParse(data []byte) (sd *Signed, err error) {
                        return
                }
        }
-       sd, err = SignedParse(data)
+       signed, err = SignedParse(data)
        if err != nil {
                return
        }
-       err = sd.CerParse()
+       err = signed.CerParse()
        return
 }
 
@@ -139,7 +143,7 @@ func (cer *CerLoad) Can(ku string) (yes bool) {
 // 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 *Signed) CerIssueWith(
+func (signed *Signed) CerIssueWith(
        parent *CerLoad,
        prv crypto.Signer,
        since, till time.Time,
@@ -149,7 +153,7 @@ func (sd *Signed) CerIssueWith(
        if err != nil {
                return err
        }
-       return sd.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
+       return signed.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
 }
 
 var ErrSigInvalid = errors.New("signature is invalid")
@@ -180,10 +184,40 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
        return
 }
 
+// Verify signature of signed data, by providing prehashed data.
+// ErrSigInvalid will be returned in case of invalid signature.
+func (cer *CerLoad) CheckSignatureDigest(digest, signature []byte) (err error) {
+       if !cer.Can(KUSig) || len(cer.Pub) != 1 {
+               err = errors.New("cer can not sign")
+               return
+       }
+       pub := cer.Pub[0]
+       var valid bool
+       switch pub.A {
+       case Ed25519BLAKE2b:
+               valid, err = ed25519blake2b.VerifyDigest(pub.V, digest, signature)
+               if !valid {
+                       err = ErrSigInvalid
+               }
+       case GOST3410256A, GOST3410512C:
+               valid, err = gost.VerifyDigest(pub.A, pub.V, digest, signature)
+               if !valid {
+                       err = ErrSigInvalid
+               }
+       default:
+               err = errors.New("unsupported signature algorithm")
+       }
+       return
+}
+
 // Verify Signed CerLoad certificate's signature with provided parent.
+// If hasher is specified, then prehashed signature mode is used.
 // Currently only single signature can be verified.
-func (sd *Signed) CerCheckSignatureFrom(parent *CerLoad) (err error) {
-       if len(sd.Sigs) != 1 {
+func (signed *Signed) CerCheckSignatureFrom(
+       parent *CerLoad,
+       hasher *hash.Hash,
+) (err error) {
+       if len(signed.Sigs) != 1 {
                err = errors.New("can verify only single signature")
                return
        }
@@ -191,37 +225,48 @@ func (sd *Signed) CerCheckSignatureFrom(parent *CerLoad) (err error) {
                err = errors.New("parent can not sign")
                return
        }
-       sig := sd.Sigs[0]
+       sig := signed.Sigs[0]
        if sig.TBS.SID != parent.Pub[0].Id {
                err = errors.New("sid != parent pub id")
                return
        }
-       tbs := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
-       buf, err := keks.EncodeBuf(tbs, nil)
-       if err != nil {
-               return
+       var tbs SignedTBS
+       if hasher == nil {
+               tbs = SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sig.TBS}
+               var buf []byte
+               buf, err = keks.EncodeBuf(tbs, nil)
+               if err != nil {
+                       return
+               }
+               return parent.CheckSignature(buf, sig.Sign.V)
+       } else {
+               tbs = SignedTBS{T: signed.Load.T, TBS: sig.TBS}
+               _, err = keks.Encode(*hasher, tbs, nil)
+               if err != nil {
+                       return
+               }
+               return parent.CheckSignatureDigest((*hasher).Sum(nil), sig.Sign.V)
        }
-       return parent.CheckSignature(buf, sig.Sign.V)
 }
 
 // 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" {
+func (signed *Signed) CerLoad() *CerLoad {
+       if signed.Load.T != "cer" || signed.Load.V == nil {
                return nil
        }
-       l, ok := sd.Load.V.(CerLoad)
+       l, ok := (*signed.Load.V).(CerLoad)
        if ok {
                return &l
        }
        return nil
 }
 
-// Verify sd Signed CerLoad certificate against cers chain of
+// Verify signed Signed CerLoad certificate against cers chain of
 // certificate authority ones at specified point of time t.
-func (sd *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
+func (signed *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
        {
-               exp := *(sd.Sigs[0].TBS.Exp)
+               exp := *(signed.Sigs[0].TBS.Exp)
                if t.Before(exp[0]) || t.Equal(exp[0]) {
                        err = errors.New("cer is not active")
                        return
@@ -231,9 +276,9 @@ func (sd *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
                        return
                }
        }
-       sid := sd.Sigs[0].TBS.SID
-       if sid == sd.CerLoad().Pub[0].Id {
-               return sd.CerCheckSignatureFrom(sd.CerLoad())
+       sid := signed.Sigs[0].TBS.SID
+       if sid == signed.CerLoad().Pub[0].Id {
+               return signed.CerCheckSignatureFrom(signed.CerLoad(), nil)
        }
        idToCer := make(map[uuid.UUID]*Signed, len(cers))
        for _, cer := range cers {
@@ -250,10 +295,10 @@ func (sd *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
        }
        signer := idToCer[sid]
        if signer == nil {
-               err = fmt.Errorf("no cer found for sid: %v", sd.Sigs[0].TBS.SID)
+               err = fmt.Errorf("no cer found for sid: %v", signed.Sigs[0].TBS.SID)
                return
        }
-       err = sd.CerCheckSignatureFrom(signer.CerLoad())
+       err = signed.CerCheckSignatureFrom(signer.CerLoad(), nil)
        if err != nil {
                return
        }
index abe3ed4945373590bd6f05aedb67fc9a91477f8507cb3f2dd8d935c235d61d97..20da835189bf13144986a5e834cb522780692cd55c5f7aecd13630444f22ceac 100755 (executable)
@@ -14,27 +14,23 @@ test_expect_success "$caAlgo: CA load generation" "certool \
     -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 \
+    -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"
+    -cer $TMPDIR/ca.cer \
+    -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer"
 test_expect_success "$caAlgo: CA self-signature" "certool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/ca.cer \
     -verify"
 
 subj="-subj CN=SubCA -subj C=RU"
-test_expect_success "$eeAlgo: SubCA generation" "certool \
+test_expect_success "$eeAlgo: SubCA load generation" "certool \
     -algo $eeAlgo \
     -ku ca -ku sig $subj \
-    -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer \
+    -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer"
+test_expect_success "$eeAlgo: SubCA generation" "certool \
+    -cer $TMPDIR/subca.cer \
     -ca-cer $TMPDIR/ca.cer -ca-prv $TMPDIR/ca.prv"
 test_expect_success "$eeAlgo: SubCA signature" "certool \
     -ca-cer $TMPDIR/ca.cer \
@@ -42,10 +38,12 @@ test_expect_success "$eeAlgo: SubCA signature" "certool \
     -verify"
 
 subj="-subj CN=EE -subj C=RU"
-test_expect_success "$eeAlgo: EE generation" "certool \
+test_expect_success "$eeAlgo: EE load generation" "certool \
     -algo $eeAlgo $subj \
-    -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
     -prv $TMPDIR/ee.prv -cer $TMPDIR/ee.cer"
+test_expect_success "$eeAlgo: EE generation" "certool \
+    -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
+    -cer $TMPDIR/ee.cer"
 test_expect_success "$eeAlgo: EE chain" "certool \
     -ca-cer $TMPDIR/ca.cer \
     -ca-cer $TMPDIR/subca.cer \
index 7b40f29c8108fc104f20d24277b21546627386033b9e51edef144317d129748a..2b84822a2013ce1d212a6fd8058960552175f9d5c26fbf78aa281b29b4fbe31d 100644 (file)
@@ -20,8 +20,10 @@ import (
        "crypto"
        "errors"
        "flag"
+       "fmt"
        "log"
        "os"
+       "sort"
        "strings"
        "time"
 
@@ -34,6 +36,7 @@ import (
 )
 
 func main() {
+       flag.Usage = usage
        ku := make(map[string]*struct{})
        subj := make(map[string]string)
        flag.Func(
@@ -69,24 +72,33 @@ func main() {
                "Optional notBefore, \"2006-01-02 15:04:05\" format")
        lifetime := flag.Uint("lifetime", 365,
                "Lifetime of the certificate, days")
-       algo := flag.String("algo", pki.GOST3410256A, "Public key algorithm")
+       algo := flag.String("algo", pki.Ed25519BLAKE2b, "Public key algorithm")
        issuingPrv := flag.String("ca-prv", "",
                "Path to private key file for issuing with")
-       reuseKey := flag.Bool("reuse-key", false,
-               "Reuse the key, do not generate new one")
        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")
+       doList := flag.Bool("list-algo", false, "List available algorithms")
 
        flag.Parse()
        log.SetFlags(log.Lshortfile)
 
-       if *cerPath == "" {
-               log.Fatal("no -cer is set")
+       if *doList {
+               algos := []string{
+                       pki.Ed25519BLAKE2b,
+                       pki.GOST3410256A,
+                       pki.GOST3410512C,
+                       pki.SNTRUP4591761X25519,
+               }
+               sort.Strings(algos)
+               for _, s := range algos {
+                       fmt.Println(s)
+               }
+               return
        }
 
-       if !*verify && len(subj) == 0 {
-               log.Fatal("no -subj is set")
+       if *cerPath == "" {
+               log.Fatal("no -cer is set")
        }
 
        var err error
@@ -104,16 +116,16 @@ func main() {
        var caPrv crypto.Signer
        var caCers []*pki.Signed
        for _, issuingCer := range issuingCers {
-               var sd *pki.Signed
-               sd, err = pki.CerParse(utils.MustReadFile(issuingCer))
+               var signed *pki.Signed
+               signed, err = pki.CerParse(utils.MustReadFile(issuingCer))
                if err != nil {
                        log.Fatal(err)
                }
-               caCers = append(caCers, sd)
+               caCers = append(caCers, signed)
        }
        if len(caCers) > 0 && !*verify {
                if *issuingPrv == "" {
-                       log.Fatal("no -ca-key is set")
+                       log.Fatal("no -ca-prv is set")
                }
                caPrv, _, err = pki.PrvParse(utils.MustReadFile(*issuingPrv))
                if err != nil {
@@ -122,30 +134,35 @@ func main() {
        }
 
        if *verify {
-               var sd *pki.Signed
-               sd, err = pki.CerParse(utils.MustReadFile(*cerPath))
+               var signed *pki.Signed
+               signed, err = pki.CerParse(utils.MustReadFile(*cerPath))
                if err != nil {
                        log.Fatal(err)
                }
-               err = sd.CerVerify(caCers, time.Now().UTC())
+               err = signed.CerVerify(caCers, time.Now().UTC())
                if err != nil {
                        log.Fatal(err)
                }
                return
        }
 
-       if *prvPath == "" {
-               log.Fatal("no -prv is set")
-       }
-
        var prvRaw []byte
-       var pub []byte
-       if *reuseKey {
-               _, pub, err = pki.PrvParse(utils.MustReadFile(*prvPath))
+       var cerLoad *pki.CerLoad
+       var signed *pki.Signed
+       if caPrv != nil {
+               signed, err = pki.CerParse(utils.MustReadFile(*cerPath))
                if err != nil {
                        log.Fatal(err)
                }
+               cerLoad = signed.CerLoad()
        } else {
+               if len(subj) == 0 {
+                       log.Fatal("no -subj is set")
+               }
+               var pub []byte
+               if *prvPath == "" {
+                       log.Fatal("no -prv is set")
+               }
                switch *algo {
                case pki.Ed25519BLAKE2b:
                        _, prvRaw, pub, err = ed25519blake2b.NewKeypair()
@@ -161,7 +178,7 @@ func main() {
                }
                {
                        var buf bytes.Buffer
-                       if _, err = keks.Encode(&buf, keks.Magic(pki.PrvKeyMagic), nil); err != nil {
+                       if _, err = keks.Encode(&buf, pki.PrvKeyMagic, nil); err != nil {
                                log.Fatal(err)
                        }
                        if _, err = keks.Encode(&buf, pki.AV{A: *algo, V: prvRaw}, nil); err != nil {
@@ -172,36 +189,38 @@ func main() {
                                log.Fatal(err)
                        }
                }
+               {
+                       pubMap := pki.Pub{A: *algo, V: pub}
+                       {
+                               av := pki.AV{A: *algo, V: pub}
+                               pubMap.Id = av.Id()
+                       }
+                       cerLoad = &pki.CerLoad{Subj: subj, Pub: []pki.Pub{pubMap}}
+               }
+               if len(ku) > 0 {
+                       cerLoad.KU = &ku
+               }
        }
 
-       pubMap := pki.Pub{A: *algo, V: pub}
        {
-               av := pki.AV{A: *algo, V: pub}
-               pubMap.Id = av.Id()
-       }
-       cerLoad := pki.CerLoad{Subj: subj, Pub: []pki.Pub{pubMap}}
-       if len(ku) > 0 {
-               cerLoad.KU = &ku
+               cerLoadAny := any(cerLoad)
+               signed = &pki.Signed{Load: pki.SignedLoad{T: "cer", V: &cerLoadAny}}
        }
-       var caCerLoad *pki.CerLoad
-       if caPrv == nil {
-               caCerLoad = &cerLoad
-       } else {
-               caCerLoad = caCers[0].CerLoad()
-       }
-       sd := pki.Signed{Load: pki.SignedLoad{T: "cer", V: cerLoad}}
+
        if caPrv != nil {
-               if err = sd.CerIssueWith(caCerLoad, caPrv, since, till); err != nil {
+               if err = signed.CerIssueWith(
+                       caCers[0].CerLoad(), caPrv, since, till,
+               ); err != nil {
                        log.Fatal(err)
                }
        }
 
        {
                var buf bytes.Buffer
-               if _, err = keks.Encode(&buf, keks.Magic(pki.CerMagic), nil); err != nil {
+               if _, err = keks.Encode(&buf, pki.CerMagic, nil); err != nil {
                        log.Fatal(err)
                }
-               if _, err = keks.Encode(&buf, sd, nil); err != nil {
+               if _, err = keks.Encode(&buf, signed, nil); err != nil {
                        log.Fatal(err)
                }
                if err = os.WriteFile(*cerPath, buf.Bytes(), 0o666); err != nil {
diff --git a/go/pki/cmd/certool/usage.go b/go/pki/cmd/certool/usage.go
new file mode 100644 (file)
index 0000000..344fbd3
--- /dev/null
@@ -0,0 +1,37 @@
+// certool -- dealing with KEKS-encoded certificates utility
+// 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 main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+)
+
+func usage() {
+       fmt.Fprintf(os.Stderr, `Usage:
+  Generate certificate load:
+    certool -prv PRV -cer CER [-algo ALGO] [-ku KU ...] \
+      -subj K=V [-subj K=V ...]
+  Sign certificate:
+    certool -cer CER -ca-prv CA-PRV -ca-cer CA-CER \
+      [-lifetime DAYS] [-since DATE]
+  Verify certificate:
+    certool -verify -cer CER CA-PRV -ca-cer CA-CER0 [-ca-cer CA-CER1 ...]
+
+`)
+       flag.PrintDefaults()
+}
index d15b15876af4fc6f013345b9cd7694e71316f10227f9c1aaf934a2dd31d820dc..2e85721bfb5ddb7c34845579a35c8f27df1434a0e696d2be8246511078bb0f1a 100644 (file)
@@ -86,6 +86,7 @@ func blake2b256() hash.Hash {
 
 func main() {
        log.SetFlags(log.Lshortfile)
+       flag.Usage = usage
        setBind := flag.String("bind", "", "Set that /bind instead of autogeneration")
        includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`)
        passwd := flag.String("passwd", "", "Passphrase")
@@ -94,12 +95,12 @@ func main() {
        balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
        doDecrypt := flag.Bool("d", false, "Decrypt")
        var pubs []*pki.Pub
-       flag.Func("pub", "Encrypt to, path to .cer", func(v string) error {
-               sd, err := pki.CerParse(utils.MustReadFile(v))
+       flag.Func("cer", "Path to certificate to encrypt to", func(v string) error {
+               signed, err := pki.CerParse(utils.MustReadFile(v))
                if err != nil {
                        return err
                }
-               load := sd.CerLoad()
+               load := signed.CerLoad()
                if load.KU == nil {
                        log.Println(v, "does not have key usages")
                } else {
@@ -115,11 +116,12 @@ func main() {
        })
        var prvs []*pki.AV
        flag.Func("prv", "Our private keys for decryption", func(v string) (err error) {
+               magic, data := keks.StripMagic(utils.MustReadFile(v))
+               if magic == "" || magic != pki.PrvKeyMagic {
+                       return errors.New("wrong magic")
+               }
                var av pki.AV
-               d := keks.NewDecoderFromBytes(
-                       utils.MustReadFile(v),
-                       &keks.DecodeOpts{MaxStrLen: 1 << 16},
-               )
+               d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
                if err = d.DecodeStruct(&av); err != nil {
                        return err
                }
@@ -200,7 +202,7 @@ func main() {
                                if len(*kem.Encap) != sntrup4591761.CiphertextSize+32 {
                                        log.Fatalln("invalid encap len")
                                }
-                               for prvIdx, prv := range prvs {
+                               for _, prv := range prvs {
                                        if len(prv.V) != sntrup4591761.PrivateKeySize+32 {
                                                log.Fatalln("invalid private keys len")
                                        }
@@ -234,9 +236,13 @@ func main() {
                                                log.Fatal(err)
                                        }
                                        {
+                                               pub := append(
+                                                       ourSNTRUP[382:],
+                                                       ourX25519.PublicKey().Bytes()...,
+                                               )
                                                ikm := bytes.Join([][]byte{
                                                        encrypted.Bind[:],
-                                                       *kem.Encap, pubs[prvIdx].V,
+                                                       *kem.Encap, pub,
                                                        keySNTRUP[:], keyX25519,
                                                }, []byte{})
                                                kek := hkdf.Extract(blake2b256,
@@ -372,7 +378,7 @@ func main() {
                }
                {
                        var hdr bytes.Buffer
-                       if _, err = keks.Encode(&hdr, keks.Magic(pki.EncryptedMagic), nil); err != nil {
+                       if _, err = keks.Encode(&hdr, pki.EncryptedMagic, nil); err != nil {
                                log.Fatal(err)
                        }
                        if _, err = keks.Encode(&hdr, &Encrypted{
diff --git a/go/pki/cmd/enctool/usage.go b/go/pki/cmd/enctool/usage.go
new file mode 100644 (file)
index 0000000..b9744cd
--- /dev/null
@@ -0,0 +1,36 @@
+// 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
+// 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 main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+)
+
+func usage() {
+       fmt.Fprintf(os.Stderr, `Usage:
+  Encrypt to recipient:
+    enctool -cer CER [-include-to] [-bind UUID] <DATA >DATA.encrypted
+  Encrypt on passphrase:
+    enctool -passwd PASSPHRASE [-bind UUID] <DATA >DATA.encrypted
+      [-balloon-s X] [-balloon-t X] [-balloon-p X]
+  Decrypt by providing possible KEMs:
+    enctool -d [-passwd ...] [-prv PRV ...] <DATA.encrypted >DATA
+
+`)
+       flag.PrintDefaults()
+}
index 93cdf5f2c690faa68fae74cf2cce861f6946c4f04a56ed54e2fb2e38111d3b3b..614536eca44bfd185ef9f08661c306357e429d71100ffb4dcdd0c1988d4361fe 100644 (file)
@@ -18,8 +18,8 @@ package main
 import (
        "bufio"
        "bytes"
-       "crypto"
        "flag"
+       "hash"
        "io"
        "log"
        "os"
@@ -31,16 +31,20 @@ import (
        "go.cypherpunks.su/keks/pki"
        pkihash "go.cypherpunks.su/keks/pki/hash"
        "go.cypherpunks.su/keks/pki/utils"
+       "go.cypherpunks.su/keks/types"
 )
 
+const BlobChunkLen = 128 * 1024
+
 func main() {
+       flag.Usage = usage
        prvPath := flag.String("prv", "", "Path to private key file")
        cerPath := flag.String("cer", "", "Path to certificate 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")
-       encryptedBindingHex := flag.String("encrypted-binding", "", "Set encrypted-binding")
+       typ := flag.String("type", "data", "Set/check the load type")
+       verify := flag.Bool("verify", false, "Do verification")
+       encryptedBindingHex := flag.String("encrypted-binding", "",
+               "Set/check encrypted-binding, UUID")
+       detached := flag.Bool("detached", false, "Detached data mode")
 
        flag.Parse()
        log.SetFlags(log.Lshortfile)
@@ -62,7 +66,8 @@ func main() {
                log.Fatal(err)
        }
 
-       var signer crypto.Signer
+       var signer pki.Signer
+       var hasher hash.Hash
        if !*verify {
                if *prvPath == "" {
                        log.Fatal("no -prv is set")
@@ -71,71 +76,142 @@ func main() {
                if err != nil {
                        log.Fatal(err)
                }
+               hasher = signer.NewHasher()
        }
 
-       hasher := pkihash.ByName(*hashAlgo)
-       if hasher == nil {
-               log.Fatal("unknown -hash specified")
-       }
-       _, err = io.Copy(hasher, bufio.NewReader(os.Stdin))
-       if err != nil {
-               log.Fatal(err)
-       }
+       stdin := bufio.NewReaderSize(os.Stdin, BlobChunkLen)
        if *verify {
-               var sd *pki.Signed
-               sd, err = pki.SignedParse(utils.MustReadFile(*sdPath))
+               decoder := keks.NewDecoderFromReader(stdin, nil)
+               var t types.Type
+               t, err = decoder.Parse()
                if err != nil {
                        log.Fatal(err)
                }
-               if len(sd.Sigs) == 0 {
-                       log.Fatal("no sigs")
+               if t != types.Magic || decoder.Iter().Magic() != pki.SignedMagic {
+                       log.Fatal("wrong magic")
                }
-               sig := sd.Sigs[0]
-               if sd.Hashes == nil || sig.TBS.Hashes == nil {
-                       log.Fatal("no hashes")
+               decoder = keks.NewDecoderFromReader(stdin, nil)
+               if _, err = decoder.Parse(); err != nil {
+                       log.Fatal(err)
                }
-               hashes := *sig.TBS.Hashes
-               hashExpect := hashes[*hashAlgo]
-               hashGot := hasher.Sum(nil)
-               if hashExpect == nil || !bytes.Equal(hashExpect, hashGot) {
-                       log.Fatal("hash mismatch")
+               var prehash pki.SignedPrehash
+               var signed pki.Signed
+               err = decoder.UnmarshalStruct(&prehash)
+               if err == nil && prehash.T == pki.SignedPrehashT {
+                       if len(prehash.Sigs) == 0 {
+                               log.Fatal("prehash: no sigs")
+                       }
+                       if len(prehash.Sigs) > 1 {
+                               log.Fatal("prehash: currently only single signature support")
+                       }
+                       for algo := range prehash.Sigs {
+                               hasher = pkihash.ByName(algo)
+                       }
+                       var blob *keks.BlobDecoder
+                       blob, err = keks.NewBlobDecoder(stdin, 1<<32)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       var chunk []byte
+                       mw := io.MultiWriter(hasher, os.Stdout)
+                       for {
+                               chunk, err = blob.Next()
+                               if err != nil {
+                                       if err != io.EOF {
+                                               log.Fatal(err)
+                                       }
+                                       break
+                               }
+                               if _, err = io.Copy(mw, bytes.NewReader(chunk)); err != nil {
+                                       log.Fatal(err)
+                               }
+                       }
+                       decoder = keks.NewDecoderFromReader(stdin, nil)
+                       err = decoder.DecodeStruct(&signed)
+               } else {
+                       err = decoder.UnmarshalStruct(&signed)
                }
-               signer := cer.CerLoad()
-               if !signer.Can(pki.KUSig) || len(signer.Pub) != 1 {
-                       log.Fatal("cer can not sign")
+               if err != nil {
+                       log.Fatal(err)
+               }
+               if err = pki.SignedValidate(&signed); err != nil {
+                       log.Fatal(err)
+               }
+               if len(signed.Sigs) == 0 {
+                       log.Fatal("no sigs")
+               }
+               if len(signed.Sigs) > 1 {
+                       log.Fatal("prehash: currently only single signature support")
                }
+               sig := signed.Sigs[0]
+               signer := cer.CerLoad()
                if sig.Sign.A != signer.Pub[0].A {
                        log.Fatal("differing signature algorithms")
                }
-               err = sd.CerCheckSignatureFrom(signer)
-               if err != nil {
+               if signed.Load.T != *typ {
+                       log.Fatalln("differing load type:", signed.Load.T)
+               }
+               if encryptedBinding != uuid.Nil {
+                       if sig.TBS.EncryptedBinding == nil {
+                               log.Fatal("missing encrypted-binding")
+                       }
+                       if *sig.TBS.EncryptedBinding != encryptedBinding {
+                               log.Fatalln("differing encrypted-binding:", *sig.TBS.EncryptedBinding)
+                       }
+               }
+               if prehash.T == "" {
+                       hasher = pkihash.ByName(sig.Sign.A)
+                       if _, err = io.Copy(hasher, stdin); err != nil {
+                               log.Fatal(err)
+                       }
+               }
+               if err = signed.CerCheckSignatureFrom(signer, &hasher); err != nil {
                        log.Fatal(err)
                }
        } else {
-               var sd pki.Signed
-               sd.Load.T = *typ
-               sdHashes := map[string]*struct{}{*hashAlgo: nil}
-               sd.Hashes = &sdHashes
-               sigHashes := map[string][]byte{*hashAlgo: hasher.Sum(nil)}
-               when := time.Now().UTC().Truncate(1000 * time.Microsecond)
-               sigTbs := pki.SigTBS{
-                       Hashes: &sigHashes,
-                       When:   &when,
+               if _, err = keks.Encode(os.Stdout, pki.SignedMagic, nil); err != nil {
+                       log.Fatal(err)
                }
+               if *detached {
+                       if _, err = io.Copy(hasher, stdin); err != nil {
+                               log.Fatal(err)
+                       }
+               } else {
+                       if _, err = keks.Encode(os.Stdout, pki.SignedPrehash{
+                               T:    pki.SignedPrehashT,
+                               Sigs: map[string]*struct{}{cer.CerLoad().Pub[0].A: nil},
+                       }, nil); err != nil {
+                               log.Fatal(err)
+                       }
+                       pr, pw := io.Pipe()
+                       br := keks.BlobReader{R: pr, ChunkLen: BlobChunkLen}
+                       copyErr := make(chan error)
+                       go func() {
+                               _, e := io.Copy(io.MultiWriter(pw, hasher), stdin)
+                               pw.Close()
+                               copyErr <- e
+                       }()
+                       _, err = keks.Encode(os.Stdout, br, nil)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       if err = <-copyErr; err != nil {
+                               log.Fatal(err)
+                       }
+               }
+               var signed pki.Signed
+               signed.Load.T = *typ
+               when := time.Now().UTC().Truncate(1000 * time.Microsecond)
+               sigTbs := pki.SigTBS{When: &when}
                if encryptedBinding != uuid.Nil {
                        sigTbs.EncryptedBinding = &encryptedBinding
                }
-               err = sd.SignWith(cer.CerLoad(), signer, sigTbs)
-               if err != nil {
+               if err = signed.SignPrehashedWith(
+                       hasher, cer.CerLoad(), signer, sigTbs,
+               ); err != nil {
                        log.Fatal(err)
                }
-               var data []byte
-               data, err = keks.EncodeBuf(sd, nil)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               err = os.WriteFile(*sdPath, data, 0o666)
-               if err != nil {
+               if _, err = keks.Encode(os.Stdout, signed, nil); err != nil {
                        log.Fatal(err)
                }
        }
diff --git a/go/pki/cmd/sigtool/usage.go b/go/pki/cmd/sigtool/usage.go
new file mode 100644 (file)
index 0000000..c3ad3d6
--- /dev/null
@@ -0,0 +1,37 @@
+// 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
+// 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 main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+)
+
+func usage() {
+       fmt.Fprintf(os.Stderr, `Usage:
+  sigtool -prv PRV -cer CER [-type TYPE] [bind] <DATA >DATA.signed
+  sigtool -verify -cer CER [-type TYPE] [bind] <DATA.signed >DATA
+  sigtool -detached -prv PRV -cer CER [-type TYPE] [bind] <DATA >DATA.signature
+  sigtool -detached -verify -cer CER [-type TYPE] [bind] <(cat DATA.signature DATA)
+
+"bind" is optional -encrypted-binding flag.
+DATA.signed holds completely encapsulated DATA.
+-detached mode keeps DATA completely separate.
+
+`)
+       flag.PrintDefaults()
+}
index f3cbc2210b8bfa83b0e7ecec1e7a2378bbd36bf87fdb55f414400926faff0225..69bafad3a473b9c73aa472f39ef3d63cd7920f4b591cb997bc7f008f2270e4aa 100644 (file)
@@ -2,7 +2,7 @@
 +++ ed25519/ed25519.go 2024-12-03 11:07:51.892841000 +0300
 @@ -20,11 +20,12 @@
        "crypto"
-       "go.cypherpunks.su/gokeks/pki/ed25519-blake2b/edwards25519"
+       "go.cypherpunks.su/keks/pki/ed25519-blake2b/edwards25519"
        cryptorand "crypto/rand"
 -      "crypto/sha512"
        "crypto/subtle"
index 92ff44932c89efbb76c5fbf654ad3cff742e11b4f5932e509db2a168ee8bf7dc..13aa582baad1887558bdf7f877088d1f44de71092ec8bb8033e5f30ec4430a34 100644 (file)
 package ed25519blake2b
 
 import (
-       "crypto"
        "crypto/rand"
 
        "go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
 )
 
-func NewKeypair() (signer crypto.Signer, prv, pub []byte, err error) {
+func NewKeypair() (signer *Signer, prv, pub []byte, err error) {
        var prvEd ed25519.PrivateKey
        var pubEd ed25519.PublicKey
        pubEd, prvEd, err = ed25519.GenerateKey(rand.Reader)
        if err != nil {
                return
        }
-       signer = prvEd
+       signer = &Signer{prvEd}
        prv = prvEd.Seed()
        pub = pubEd[:]
        return
index 666b0539fe47713db6a38a9a2afc4cd1081b38f2585eaf7cccce9914d02f9eae..b53ae56dbed68416af33a1b1ef0c01d3ffcbe10602b0f885221c4a25a0110f28 100755 (executable)
@@ -4,7 +4,7 @@
 # That script copies the library (tested on 1.23.3) and patches it to
 # use BLAKE2b hash.
 
-modname=go.cypherpunks.su/gokeks/pki/ed25519-blake2b
+modname=go.cypherpunks.su/keks/pki/ed25519-blake2b
 go mod init $modname
 dst=$PWD
 cd $(go env GOROOT)/src
index c25e7e65d3b6515f897f9d27e5088b15718ebd191b75a16b6ba65549831dc241..d9c62d861859f6dabaaada6d9053c0d2d9f46f3225496d708539dc1375f2b995 100644 (file)
@@ -18,17 +18,48 @@ package ed25519blake2b
 import (
        "crypto"
        "errors"
+       "hash"
+       "io"
 
        "go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
+       "golang.org/x/crypto/blake2b"
 )
 
-func NewSigner(v []byte) (prv crypto.Signer, pub []byte, err error) {
+type Signer struct {
+       Prv ed25519.PrivateKey
+}
+
+func (s *Signer) Public() crypto.PublicKey {
+       return s.Prv.Public()
+}
+
+func (s *Signer) Sign(
+       rand io.Reader,
+       msg []byte,
+       opts crypto.SignerOpts,
+) (signature []byte, err error) {
+       return s.Prv.Sign(rand, msg, opts)
+}
+
+func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
+       return s.Prv.Sign(rand, dgst, &ed25519.Options{Hash: crypto.BLAKE2b_512})
+}
+
+func (s *Signer) NewHasher() hash.Hash {
+       h, err := blake2b.New512(nil)
+       if err != nil {
+               panic(err)
+       }
+       return h
+}
+
+func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
        if len(v) != ed25519.SeedSize {
                err = errors.New("wrong ed25519 private key size")
                return
        }
        p := ed25519.NewKeyFromSeed(v)
        pub = p[ed25519.SeedSize:]
-       prv = p
+       prv = &Signer{p}
        return
 }
index afec09ea0906594330730734254df62a420dc54c0f46a8fdba72fc0a13bdaec5..662c7f4b6e67df2b505933cc4597ce659f19d6adfcc72807ea294f13a8cba645 100644 (file)
@@ -16,6 +16,7 @@
 package ed25519blake2b
 
 import (
+       "crypto"
        "errors"
 
        "go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
@@ -29,3 +30,25 @@ func Verify(pub, signed, signature []byte) (valid bool, err error) {
        valid = ed25519.Verify(ed25519.PublicKey(pub), signed, signature)
        return
 }
+
+func VerifyDigest(pub, digest, signature []byte) (valid bool, err error) {
+       if len(pub) != ed25519.PublicKeySize {
+               err = errors.New("invalid ed25519 public key size")
+               return
+       }
+       err = ed25519.VerifyWithOptions(
+               ed25519.PublicKey(pub),
+               digest,
+               signature,
+               &ed25519.Options{Hash: crypto.BLAKE2b_512},
+       )
+       if err == nil {
+               valid = true
+       } else {
+               if err.Error() == "ed25519: invalid signature" {
+                       valid = false
+                       err = nil
+               }
+       }
+       return
+}
index a9836dc954269378d365852eabf3c8bb5e688e1334dedef2d1379fdda93147cc..b89c25563a0612a54fde4c0cb3b00076beb4f1e051f0f22a70a087fd31d569cd 100644 (file)
@@ -1,7 +1,6 @@
 package gost
 
 import (
-       "crypto"
        "crypto/rand"
        "errors"
        "io"
@@ -9,7 +8,7 @@ import (
        "go.cypherpunks.su/gogost/v6/gost3410"
 )
 
-func NewKeypair(algo string) (signer crypto.Signer, prv, pub []byte, err error) {
+func NewKeypair(algo string) (signer *Signer, prv, pub []byte, err error) {
        curve := CurveByName(algo)
        if curve == nil {
                err = errors.New("unknown curve")
index 2f7436b8c85cd3d5c35a41bc206d276934feabe4eb9b093882eb9feca9c8c4a0..b8696314098c25c616392893c277a458aa76851d5a26d5e40e755e51084c45ff 100644 (file)
@@ -36,7 +36,20 @@ func (s *Signer) Sign(
        return
 }
 
-func NewSigner(a string, v []byte) (prv crypto.Signer, pub []byte, err error) {
+func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
+       signature, err = s.Prv.SignDigest(dgst, rand)
+       if err != nil {
+               return
+       }
+       signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...)
+       return
+}
+
+func (s *Signer) NewHasher() hash.Hash {
+       return s.Hasher()
+}
+
+func NewSigner(a string, v []byte) (prv *Signer, pub []byte, err error) {
        signer := Signer{}
        switch a {
        case GOST3410256A:
index a307b55e8b05df1ca01b5013cae5d744e9b7d781d53b7d4005c005e22e936f1f..6046b02be82f7aba429a0ebd2726ffd764e1c62f4e7f8869aa8a244b8d7fa1e6 100644 (file)
@@ -28,3 +28,14 @@ func Verify(algo string, pub, signed, signature []byte) (valid bool, err error)
                append(signature[len(signature)/2:], signature[:len(signature)/2]...))
        return
 }
+
+func VerifyDigest(algo string, pub, digest, signature []byte) (valid bool, err error) {
+       var pk *gost3410.PublicKey
+       pk, err = gost3410.NewPublicKeyBE(CurveByName(algo), pub)
+       if err != nil {
+               return
+       }
+       valid, err = pk.VerifyDigest(digest,
+               append(signature[len(signature)/2:], signature[:len(signature)/2]...))
+       return
+}
index c83e58f9926c54e339b643cff88f99b7b4c55160518af7aab97302312795b2f2..fdde23bc15243715e4d91db15204f52e156e393a1ed490a65ac64d280ee60fba 100644 (file)
@@ -21,6 +21,9 @@ import (
        "go.cypherpunks.su/gogost/v6/gost34112012256"
        "go.cypherpunks.su/gogost/v6/gost34112012512"
        "golang.org/x/crypto/blake2b"
+
+       ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
+       "go.cypherpunks.su/keks/pki/gost"
 )
 
 const (
@@ -32,11 +35,11 @@ const (
 
 func ByName(name string) hash.Hash {
        switch name {
-       case Streebog256:
+       case Streebog256, gost.GOST3410256A:
                return gost34112012256.New()
-       case Streebog512:
+       case Streebog512, gost.GOST3410512C:
                return gost34112012512.New()
-       case BLAKE2b:
+       case BLAKE2b, ed25519blake2b.Ed25519BLAKE2b:
                h, err := blake2b.New512(nil)
                if err != nil {
                        panic(err)
index fd8f885070c0b2765059180e3bc0916c64d811e984f99ab1fc37159fd4f2d7d4..94acb28c22bb20ef5edeb8ff2a08a9a687bb3e1539b3cc2ce69993cea98b12db 100644 (file)
@@ -16,7 +16,6 @@
 package pki
 
 import (
-       "crypto"
        "errors"
        "fmt"
 
@@ -26,7 +25,7 @@ import (
 )
 
 // Parse private key contained in AV KEKS-encoded structure.
-func PrvParse(data []byte) (prv crypto.Signer, pub []byte, err error) {
+func PrvParse(data []byte) (prv Signer, pub []byte, err error) {
        {
                var magic keks.Magic
                magic, data = keks.StripMagic(data)
index 4de2cd85eb99b52704bd4e2de347ae1ddd7f49924579f88378e85737001d10b7..922579f22192ba78a4638cfa4d06cc76eb013891b2953eca1771a829a4dc40cb 100644 (file)
@@ -19,6 +19,8 @@ import (
        "crypto"
        "crypto/rand"
        "errors"
+       "hash"
+       "io"
        "time"
 
        "github.com/google/uuid"
@@ -26,19 +28,31 @@ import (
        "go.cypherpunks.su/keks"
 )
 
-const SignedMagic = "pki/signed"
+const SignedMagic = keks.Magic("pki/signed")
+
+type Signer interface {
+       crypto.Signer
+       SignDigest(io.Reader, []byte) ([]byte, error)
+       NewHasher() hash.Hash
+}
+
+const SignedPrehashT = "prehash"
+
+type SignedPrehash struct {
+       T    string               `keks:"t"`
+       Sigs map[string]*struct{} `keks:"sigs"`
+}
 
 type SignedLoad struct {
-       V any    `keks:"v,omitempty"`
+       V *any   `keks:"v,omitempty"`
        T string `keks:"t"`
 }
 
 type SigTBS struct {
-       Hashes *map[string][]byte `keks:"hash,omitempty"`
-       CID    *uuid.UUID         `keks:"cid,omitempty"`
-       Exp    *[]time.Time       `keks:"exp,omitempty"`
-       When   *time.Time         `keks:"when,omitempty"`
-       SID    uuid.UUID          `keks:"sid"`
+       CID  *uuid.UUID   `keks:"cid,omitempty"`
+       Exp  *[]time.Time `keks:"exp,omitempty"`
+       When *time.Time   `keks:"when,omitempty"`
+       SID  uuid.UUID    `keks:"sid"`
 
        EncryptedBinding *uuid.UUID `keks:"encrypted-binding,omitempty"`
 }
@@ -50,45 +64,36 @@ type Sig struct {
 }
 
 type SignedTBS struct {
-       V   any    `keks:"v"`
+       V   any    `keks:"v,omitempty"`
        T   string `keks:"t"`
        TBS SigTBS `keks:"tbs"`
 }
 
 type Signed struct {
-       Hashes *map[string]*struct{} `keks:"hash,omitempty"`
-       Cers   *[]*Signed            `keks:"certs,omitempty"`
-       Load   SignedLoad            `keks:"load"`
-       Sigs   []*Sig                `keks:"sigs"`
+       Cers *[]*Signed `keks:"certs,omitempty"`
+       Load SignedLoad `keks:"load"`
+       Sigs []*Sig     `keks:"sigs"`
 }
 
 // Validate parsed pki-signed structure.
-func SignedValidate(sd *Signed) (err error) {
-       if sd.Hashes != nil && len(*sd.Hashes) == 0 {
-               err = errors.New("SignedParse: empty /hash")
-               return
-       }
-       if sd.Cers != nil {
-               if len(*sd.Cers) == 0 {
+func SignedValidate(signed *Signed) (err error) {
+       if signed.Cers != nil {
+               if len(*signed.Cers) == 0 {
                        err = errors.New("SignedParse: empty /certs")
                        return
                }
-               for _, cer := range *sd.Cers {
+               for _, cer := range *signed.Cers {
                        err = cer.CerParse()
                        if err != nil {
                                return
                        }
                }
        }
-       for _, sig := range sd.Sigs {
+       for _, sig := range signed.Sigs {
                if sig.CerLoc != nil && len(*sig.CerLoc) == 0 {
                        err = errors.New("SignedParse: empty cer-loc")
                        return
                }
-               if sig.TBS.Hashes != nil && len(*sig.TBS.Hashes) == 0 {
-                       err = errors.New("SignedParse: empty hash")
-                       return
-               }
                if sig.TBS.Exp != nil {
                        if len(*sig.TBS.Exp) != 2 {
                                err = errors.New("SignedParse: wrong exp len")
@@ -101,23 +106,6 @@ func SignedValidate(sd *Signed) (err error) {
                                }
                        }
                }
-               if sd.Hashes != nil {
-                       if sig.TBS.Hashes == nil {
-                               err = errors.New("SignedParse: /sigs: no hash")
-                               return
-                       }
-                       var exists bool
-                       for ai := range *sd.Hashes {
-                               if _, ok := (*sig.TBS.Hashes)[ai]; ok {
-                                       exists = true
-                                       break
-                               }
-                       }
-                       if !exists {
-                               err = errors.New("SignedParse: /sigs: no hash")
-                               return
-                       }
-               }
        }
        return
 }
@@ -133,19 +121,19 @@ func SignedParse(data []byte) (*Signed, error) {
                }
        }
        d := keks.NewDecoderFromBytes(data, nil)
-       var sd Signed
-       err := d.DecodeStruct(&sd)
+       var signed Signed
+       err := d.DecodeStruct(&signed)
        if err != nil {
                return nil, err
        }
-       err = SignedValidate(&sd)
-       return &sd, err
+       err = SignedValidate(&signed)
+       return &signed, err
 }
 
 // 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 *Signed) SignWith(
+// to the signed.Sigs. parent certificate must have "sig" key-usage.
+func (signed *Signed) SignWith(
        parent *CerLoad,
        prv crypto.Signer,
        sigTBS SigTBS,
@@ -154,11 +142,11 @@ func (sd *Signed) SignWith(
                return errors.New("parent can not sign")
        }
        sigTBS.SID = parent.Pub[0].Id
-       sdTBS := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sigTBS}
+       signedTBS := SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sigTBS}
        sig := Sig{TBS: sigTBS}
        sig.Sign.A = parent.Pub[0].A
        var buf []byte
-       buf, err = keks.EncodeBuf(sdTBS, nil)
+       buf, err = keks.EncodeBuf(signedTBS, nil)
        if err != nil {
                return
        }
@@ -166,6 +154,32 @@ func (sd *Signed) SignWith(
        if err != nil {
                return
        }
-       sd.Sigs = append(sd.Sigs, &sig)
+       signed.Sigs = append(signed.Sigs, &sig)
+       return nil
+}
+
+// Same as SignWith, but use prehashed mode.
+func (signed *Signed) SignPrehashedWith(
+       hasher hash.Hash,
+       parent *CerLoad,
+       prv Signer,
+       sigTBS SigTBS,
+) (err error) {
+       if !parent.Can(KUSig) || len(parent.Pub) != 1 {
+               return errors.New("parent can not sign")
+       }
+       sigTBS.SID = parent.Pub[0].Id
+       signedTBS := SignedTBS{T: signed.Load.T, TBS: sigTBS}
+       sig := Sig{TBS: sigTBS}
+       sig.Sign.A = parent.Pub[0].A
+       _, err = keks.Encode(hasher, signedTBS, nil)
+       if err != nil {
+               return
+       }
+       sig.Sign.V, err = prv.SignDigest(rand.Reader, hasher.Sum(nil))
+       if err != nil {
+               return
+       }
+       signed.Sigs = append(signed.Sigs, &sig)
        return nil
 }
index c9281fe3292d01a0992a2f2b71756dc82a130ea03470de833c72dd514c366dca..8e3e2285c7e1b83dcacfd79443928bb7885b8e71feaf87b616036a4a924cbee4 100644 (file)
@@ -9,5 +9,5 @@ cer-load = {
     * text => any
 }
 
-ku = "ca" / "sig" / "app-name" / text
+ku = "ca" / "sig" / "kem" / "app-name" / text
 crit-ext-type = text
index 1c9d9a97d8892ce3c92efc1c7558c484ff7e4383f6e4c73e781617787f0b3125..b180ae01d70f82be6138e746ad72a440e5229cdac28a326cd0ae7051b5faee9c 100644 (file)
@@ -8,6 +8,10 @@ Stored in a file, it should begin with "pki/cer" @ref{Magic, magic}.
 Its @code{/load/t} equals to @code{cer}.
 @code{/load/v} contains @code{cer-load}:
 
+@verbatim
+cer = pki-signed ; with /load/t = "cer", /load/v = cer-load
+@end verbatim
+
 @verbatiminclude format/cer-load.cddl
 
 @table @code
index 829423c2b3d58c2aea0dd44ec79ab2f7c4c729bd54db644e89ff4e5ea824cb36..0122491e1643476aea23a14980f48e21e3d7cd9b48afa4a09b93e290a84834be 100644 (file)
@@ -28,7 +28,7 @@ kem-generic = {
     * text => any
 }
 
-kem-balloon-blake2b = {
+kem-balloon-blake2b-hkdf = {
     a: "balloon-blake2b-hkdf",
     cek: bytes,
     cost: {
index 1933d1a8e07486edd96fb37def26daf6f85f19db77f1e1818bf9e3322ce414f9..d41ce92ffd1ac581c1d12096b34271cb43708d3d684885c3a8964c32cd5968bf 100644 (file)
@@ -2,8 +2,6 @@
 @cindex private-key
 @section private-key format
 
-Private key is stored in trivial map:
-
 @verbatiminclude format/private-key.cddl
 
 Stored in a file, it should begin with "pki/prvkey" @ref{Magic, magic}.
@@ -13,7 +11,7 @@ Stored in a file, it should begin with "pki/prvkey" @ref{Magic, magic}.
 
 Big-endian private key representation must be used.
 
-Following algorithm identifiers are acceptable:
+Following algorithm identifiers are used:
 @code{gost3410-256A}, @code{gost3410-512C}.
 
 @node private-key-ed25519-blake2b
index 44391027482615e7577b7d913bef747cc850d3b9c6bf994d1bcc8c39ffe78bcb..ba42874ac8ffad5cd0b576a95f00607d02fec479223046dd59f06f9413a73df6 100644 (file)
@@ -21,9 +21,7 @@ There is example registry of known algorithm identifiers.
 @item skein512
     @code{@ref{pki-hashed-skein512}}
 @item streebog256, streebog512
-    @code{@ref{cer-gost3410}},
-    @code{@ref{pki-hashed-gost3411}},
-    @code{@ref{pki-signed-gost3410}}
+    @code{@ref{pki-hashed-gost3411}}
 @item xxh3-128
     @code{@ref{pki-hashed-xxh3-128}}
 @end table
diff --git a/spec/format/signed-prehash.cddl b/spec/format/signed-prehash.cddl
new file mode 100644 (file)
index 0000000..0d3c686
--- /dev/null
@@ -0,0 +1,4 @@
+pki-signed-prehash = {
+    t: "prehash",
+    sigs: set, ; set of signature algorithm identifiers (/sigs/*/sign/a)
+}
index 137d8a66c8beec6bb7dd04922f899ed80d587444502e80cb8cbf425ed03b4ccc..94e8a043d7300726a75181156ae3fe677c1a46691e424b00e2deb6659a20b43b 100644 (file)
@@ -1,5 +1,5 @@
 pki-signed-tbs = {
     t: text, ; = /load/t
-    v: nil / any,
+    ? v: any, ; /load/v if it is supplied
     tbs: map, ; = /sigs/?/tbs
 }
index 6579cb1bf2160a2bba06112cc7de86fbdd13442f4d52c71498abcc85d5fa2fca..cb7077d8acdeaaf61bcadc16fda1ea53a282374534589f41af20fa03a618e438 100644 (file)
@@ -1,21 +1,17 @@
 ai = text ; algorithm identifier
-av = {a: ai, v: bytes}
 
 pki-signed = {
-    ? hash: set, ; when using prehashing
     load: {
         t: text,
-        ? v: bytes / text / blob / map / list,
+        ? v: bytes / text / map / list,
     },
     sigs: [+ sig],
     ? certs: [+ cer],
 }
 
-cer = pki-signed ; with /load/t = "cer", /load/v = cer-load
-
 sig = {
     tbs: sig-tbs,
-    sign: av,
+    sign: {a: ai, v: bytes},
     ? cer-loc: [+ url],
     * text => any
 }
@@ -24,8 +20,7 @@ url = text
 
 sig-tbs = {
     sid: uuid, ; signer's public key id
-    ? hash: {ai => bytes}, ; when using prehashing
-    ? envelope-binding: uuid,
+    ? encrypted-binding: uuid,
     ? when: tai64 / tai64n,
     * text => any
 }
index af5d1c9c92b586cbcb32adde00578d7e83549499494f2f12936636d830f979ce..52f413fffa5b6fb00b16d54629115bf55d9be18d06729d36dc96dee84d528e1a 100644 (file)
@@ -6,7 +6,7 @@ That resembles @url{https://datatracker.ietf.org/doc/html/rfc5652, CMS}
 (PKCS#7) ASN.1-based format.
 
 Stored in a file, it should begin with "pki/signed" @ref{Magic, magic},
-unless this is a @ref{cer, certificate}.
+unless it is a @ref{cer, certificate}.
 
 @verbatiminclude format/signed.cddl
 
@@ -14,14 +14,22 @@ Signature is created by signing the following encoded MAP:
 
 @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
-MAP or LIST, then its encoded binary representation is hashed. If it is
-BIN/STR or BLOB, then its binary contents are hashed. So signature will
-stay the same even if data is converted from BIN to BLOB.
+If no @code{/load/v} is provided, then the data is detached from the
+@code{pki-signed} structure itself and it is fed into hasher before that
+structure. You can provide it any way you wish, but for keeping that
+detached data closely to the @code{pki-signed}, you should use the
+following approach:
 
-If @code{/load/v} is absent, then it is detached and must be provided as
-a binary data from outside.
+@verbatim
+pki-signed-prehash || BLOB || pki-signed
+@end verbatim
+
+@verbatiminclude format/signed-prehash.cddl
+
+With @code{pki-signed-prehash} you initialise your hashers used during
+signing process and fed BLOB's contents (not the encoded BLOB itself!)
+into the them. Finally you add @code{pki-signed-tbs} without @code{v}
+field to them.
 
 @code{/sigs/*/tbs/when} is optional signing time.
 
@@ -35,7 +43,7 @@ help creating the whole verification chain. They are placed outside
 
 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.
+@ref{pki-encrypted, encrypted}'s @code{/bind} value.
 
 @node pki-signed-gost3410
 @subsection pki-signed with GOST R 34.10-2012
@@ -45,9 +53,6 @@ function. Its digest must be big-endian serialised. Public key must be
 in @code{BE(X)||BE(Y)} format. Signature is in @code{BE(R)||BE(S)}
 format.
 
-Following algorithm identifiers should be used for the hash:
-@code{streebog256}, @code{streebog512}.
-
 Following algorithm identifiers are acceptable for the public key and
 signature: @code{gost3410-256A}, @code{gost3410-512C}.
 
@@ -61,4 +66,7 @@ But BLAKE2b is used instead of SHA2-512 hash.
 Strict @url{https://zips.z.cash/zip-0215, ZIP-0215} validation rules
 should be used while verifying the signature.
 
+PureEdDSA @strong{must} be used when no detached data exists.
+HashEdDSA @strong{must} be used otherwise, using BLAKE2b-512 as a hash.
+
 @code{ed25519-blake2b} algorithm identifier is used.