]> Cypherpunks repositories - keks.git/commitdiff
Yet another revised certificate format
authorSergey Matveev <stargrave@stargrave.org>
Sat, 12 Oct 2024 14:11:35 +0000 (17:11 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 13 Oct 2024 16:22:58 +0000 (19:22 +0300)
21 files changed:
gyac/cmd/test-vector-anys/main.go
gyac/cmd/test-vector-manual/main.go
gyac/dec.go
gyac/enc.go
gyac/reflect.go
gyac/yacpki/algo.go
gyac/yacpki/cer.go
gyac/yacpki/cmd/yacertool/basic.t
gyac/yacpki/cmd/yacertool/main.go
gyac/yacpki/cmd/yacsdtool/main.go [new file with mode: 0644]
gyac/yacpki/signed-data.go [new file with mode: 0644]
gyac/yacpki/utils/utils.go [new file with mode: 0644]
spec/format/cer-load.cddl
spec/format/cer-sig-tbs.cddl [new file with mode: 0644]
spec/format/cer.texi
spec/format/hashed-data.texi
spec/format/private-key.cddl
spec/format/private-key.texi
spec/format/signed-data-sig-tbs.cddl
spec/format/signed-data.cddl
spec/format/signed-data.texi

index 92a2cdeaf6dd6789f46c9bf275946e045b189840074a8eb02599d2b5746f3c36..0f63d6bbe1fa39ca10e7bde32fd63d4f4416c6bbf513c2390f8d0679d902f949 100644 (file)
@@ -57,7 +57,7 @@ func main() {
                        []any{},
                        map[string]any{},
                        gyac.MakeBlob(123, []byte{}),
-                       uuid.MustParse("00000000-0000-0000-0000-000000000000"),
+                       uuid.Nil,
                        &gyac.Raw{
                                T: gyac.AtomTAI64,
                                V: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
index b053753dc20d0d75aa337615fed08187dd2f1c4fd7471be43f3bd7120bf22a9b..aa0048b6a93cbd677937af81af986d27cf083a280e0b62665e29fb3257db4a43 100644 (file)
@@ -169,8 +169,7 @@ func main() {
                                buf = gyac.AtomBlobEncode(buf, 123)
                                buf = gyac.AtomBinEncode(buf, []byte{})
                        }
-                       buf = gyac.AtomUUIDEncode(buf,
-                               uuid.MustParse("00000000-0000-0000-0000-000000000000"))
+                       buf = gyac.AtomUUIDEncode(buf, uuid.Nil)
                        buf = gyac.AtomRawEncode(buf, &gyac.Raw{
                                T: gyac.AtomTAI64,
                                V: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
index ef552b8b764493ea921ee80f30e1c4eb98cc75511cdc3e14df813cdbd24e097b..8f41f715c413fd9eb7d89e0c1a5b87089199c13ffb90c58c6777f3642aaeaac6 100644 (file)
@@ -51,6 +51,10 @@ type Item struct {
        T byte
 }
 
+func (i *Item) Typ() ItemType {
+       return ItemType(i.T)
+}
+
 type Raw struct {
        V []byte
        T AtomType
@@ -121,7 +125,7 @@ func AtomDecode(buf []byte) (item *Item, off int, err error) {
                        err = ErrNotEnough
                        return
                }
-               if ItemType(item.T) == ItemBin {
+               if item.Typ() == ItemBin {
                        item.V = buf[1+ll : 1+ll+l]
                } else {
                        s := unsafe.String(unsafe.SliceData(buf[1+ll:]), l)
@@ -172,7 +176,7 @@ func AtomDecode(buf []byte) (item *Item, off int, err error) {
                        err = ErrLenNonMinimal
                        return
                }
-               if ItemType(item.T) == ItemUInt {
+               if item.Typ() == ItemUInt {
                        item.V = v
                } else {
                        item.V = -1 - int64(v)
@@ -293,7 +297,7 @@ func DecodeItem(buf []byte) (item *Item, tail []byte, err error) {
        }
        buf = buf[off:]
        tail = buf
-       switch ItemType(item.T) {
+       switch item.Typ() {
        case ItemList:
                var sub *Item
                var v []*Item
index 8dca15afb57fd0ad793a3f1706e49c3358bd33e1f4b59d1f319c78eb3bd0d47b..b59d8deb18e3fc9abff9ff5536f186d67500ebf94f3b122dc54675d6c30358a6 100644 (file)
@@ -273,7 +273,7 @@ func AtomRawEncode(buf []byte, raw *Raw) []byte {
 }
 
 func EncodeItem(buf []byte, item *Item) []byte {
-       switch ItemType(item.T) {
+       switch item.Typ() {
        case ItemNIL:
                return AtomNILEncode(buf)
        case ItemBool:
index d4f95148c8ea0731b0062d8432428588ba65d312efd8a9789784dfc671dd2e0a..3d21c3f5b63beb2c5e8a5ed3a722e3ae127efee5e94cc296104e876f2b1ec261 100644 (file)
@@ -152,6 +152,8 @@ func ItemFromGo(v any) *Item {
                                var empty bool
                                item := ItemFromGo(fv.Interface())
                                switch ItemType(item.T) {
+                               case ItemNIL:
+                                       empty = true
                                case ItemList:
                                        if len(item.V.([]*Item)) == 0 {
                                                empty = true
@@ -231,7 +233,7 @@ func DecodeToStruct(dst any, raw []byte) (tail []byte, err error) {
        if err != nil {
                return
        }
-       if ItemType(item.T) != ItemMap {
+       if item.Typ() != ItemMap {
                err = errors.New("non-map")
                return
        }
index 96ece9cb9685bec525940378673e4766d7129bc6218124d31256d71e1d6d7043..99997a0dc5236ba0ec90ef3b73fa7bef665b08993b7d2f5ec3131ba9ebfd36b1 100644 (file)
@@ -1,12 +1,16 @@
 package yacpki
 
 import (
+       "bytes"
        "hash"
        "log"
 
+       "github.com/google/uuid"
        "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/gogost/v6/gost34112012256"
        "go.cypherpunks.su/gogost/v6/gost34112012512"
+       "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
 const (
@@ -31,6 +35,22 @@ type AV struct {
        V []byte `yac:"v"`
 }
 
+func (av *AV) Id() (id uuid.UUID) {
+       var hasher hash.Hash
+       switch av.A {
+       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D, AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
+               hasher = gost34112012256.New()
+       default:
+               panic("unsupported algorithm")
+       }
+       utils.MustWrite(hasher, gyac.EncodeItem(nil, gyac.ItemFromGo(av)))
+       id, err := uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil)))
+       if err != nil {
+               panic(err)
+       }
+       return id
+}
+
 func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
        switch name {
        case AlgoGOST3410256A:
@@ -47,6 +67,8 @@ func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
                curve = gost3410.CurveIdtc26gost341012512paramSetB()
        case AlgoGOST3410512C:
                curve = gost3410.CurveIdtc26gost341012512paramSetC()
+       default:
+               log.Fatal("unknown curve")
        }
        return
 }
@@ -58,7 +80,7 @@ func HasherByKeyAlgo(a string) hash.Hash {
        case AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
                return gost34112012512.New()
        default:
-               log.Fatal("unsupported CA algorithm")
+               log.Fatal("unsupported algorithm")
        }
        return nil
 }
index 1d3991f40ae9ebed38ad563e2d718b7c87c373b1956b375f015e6b1d9dc6a664..c3675c8a286adfb95181402bc3b52e7ab44842b36f04cc98694d45fd3ddf31eb 100644 (file)
 package yacpki
 
 import (
-       "bytes"
        "crypto"
-       "crypto/rand"
        "errors"
-       "hash"
+       "fmt"
        "time"
 
        "github.com/google/uuid"
        "go.cypherpunks.su/gogost/v6/gost3410"
-       "go.cypherpunks.su/gogost/v6/gost34112012256"
        "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
-type SignedDataLoad struct {
-       V any    `yac:"v"`
-       T string `yac:"t"`
-}
-
-type SigTBS struct {
-       Hashes map[string][]byte `yac:"hash,omitempty"`
-       SID    uuid.UUID         `yac:"sid"`
-}
+const (
+       KUCA   = "ca"
+       KUSign = "sig"
+)
 
-type Sig struct {
-       TBS    SigTBS   `yac:"tbs,omitempty"`
-       CerLoc []string `yac:"cer-loc,omitempty"`
-       Sign   AV       `yac:"sign"`
+type Pub struct {
+       A  string    `yac:"a"`
+       V  []byte    `yac:"v"`
+       Id uuid.UUID `yac:"id"`
 }
 
-type SignedDataTBS struct {
-       V   any    `yac:"v"`
-       T   string `yac:"t"`
-       TBS SigTBS `yac:"tbs"`
+type CerLoad struct {
+       KU   *map[string]*struct{} `yac:"ku,omitempty"`
+       Subj map[string]string     `yac:"sub"`
+       Crit *[]map[string]any     `yac:"crit,omitempty"`
+       Pub  []Pub                 `yac:"pub"`
 }
 
-type SignedData struct {
-       Hashes []string       `yac:"hash,omitempty"`
-       Load   SignedDataLoad `yac:"load"`
-       Sigs   []Sig          `yac:"sigs"`
-       Cers   []SignedData   `yac:"certs,omitempty"`
+func CerParse(data []byte) (sd *SignedData, tail []byte, err error) {
+       sd, tail, err = SignedDataParse(data)
+       if err != nil {
+               return
+       }
+       err = sd.CerParse()
+       return
 }
 
-type CerLoad struct {
-       KU   []string          `yac:"ku,omitempty"`
-       Subj map[string]string `yac:"sub"`
-       Exp  []time.Time       `yac:"exp"`
-       Crit []map[string]any  `yac:"crit,omitempty"`
-       Pub  AV                `yac:"pub"`
-       PKID uuid.UUID         `yac:"pkid"`
+func (sd *SignedData) CerParse() error {
+       if sd.Load.T != "cer" {
+               return errors.New("CerParse: wrong load type")
+       }
+       if len(sd.Sigs) == 0 {
+               return errors.New("CerParse: missing sigs")
+       }
+       for _, sig := range sd.Sigs {
+               if sig.TBS.Hashes != nil {
+                       return errors.New("CerParse: prehashed SignedData")
+               }
+               if sig.TBS.CID == nil {
+                       return errors.New("CerParse: missing cid")
+               }
+               if sig.TBS.Exp == nil {
+                       return errors.New("CerParse: missing exp")
+               }
+       }
+       var load CerLoad
+       var err error
+       if v, ok := sd.Load.V.(map[string]any); ok {
+               err = gyac.MapToStruct(&load, v)
+       } else {
+               err = errors.New("CerParse: wrong /load/v")
+       }
+       if err != nil {
+               return err
+       }
+       sd.Load.V = load
+       if load.KU != nil {
+               if len(*load.KU) == 0 {
+                       return errors.New("CerParse: empty ku")
+               }
+               for _, v := range *load.KU {
+                       if v != nil {
+                               return errors.New("CerParse: non-nil ku value")
+                       }
+               }
+       }
+       if len(load.Subj) == 0 {
+               return errors.New("CerParse: empty sub")
+       }
+       if load.Crit != nil {
+               if len(*load.Crit) == 0 {
+                       return errors.New("CerParse: empty crit")
+               }
+               return errors.New("CerParse: currently no critical extensions are supported")
+       }
+       if len(load.Pub) == 0 {
+               return errors.New("CerParse: empty pub")
+       }
+       for _, pub := range load.Pub {
+               if len(pub.A) == 0 || len(pub.V) == 0 || pub.Id == uuid.Nil {
+                       return errors.New("CerParse: non-filled pub")
+               }
+       }
+       return nil
 }
 
-func (tbs *CerLoad) HasCA() (hasCA bool) {
-       for _, ku := range tbs.KU {
-               if ku == "ca" {
-                       hasCA = true
-               }
+func (cer *CerLoad) CanSign() bool {
+       if cer.KU == nil {
+               return false
        }
-       return
+       if _, ok := (*cer.KU)[KUSign]; !ok {
+               return false
+       }
+       if len(cer.Pub) != 1 {
+               return false
+       }
+       return true
 }
 
-func PKIDFromPub(pub *AV) (kid uuid.UUID) {
-       hasher := gost34112012256.New()
-       hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(pub)))
-       var err error
-       kid, err = uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil)))
+func (sd *SignedData) CerIssueWith(
+       parent *CerLoad,
+       prv crypto.Signer,
+       since, till time.Time,
+) error {
+       exp := []time.Time{since, till}
+       cid, err := uuid.NewV7()
        if err != nil {
-               panic(err)
+               return err
        }
-       return
+       return sd.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
 }
 
-var SigInvalid = errors.New("signature is invalid")
+var ErrSigInvalid = errors.New("signature is invalid")
 
 func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
-       switch cer.Pub.A {
+       if !cer.CanSign() {
+               err = errors.New("cer can not sign")
+               return
+       }
+       pub := cer.Pub[0]
+       switch pub.A {
        case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D, AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
-               var pub *gost3410.PublicKey
-               pub, err = gost3410.NewPublicKeyBE(GOST3410CurveByName(cer.Pub.A), cer.Pub.V)
+               var pk *gost3410.PublicKey
+               pk, err = gost3410.NewPublicKeyBE(GOST3410CurveByName(pub.A), pub.V)
                if err != nil {
                        return
                }
-               hasher := HasherByKeyAlgo(cer.Pub.A)
-               hasher.Write(signed)
+               hasher := HasherByKeyAlgo(pub.A)
+               utils.MustWrite(hasher, signed)
                var valid bool
-               valid, err = pub.VerifyDigest(hasher.Sum(nil), signature)
+               valid, err = pk.VerifyDigest(hasher.Sum(nil), signature)
                if !valid {
-                       err = SigInvalid
+                       err = ErrSigInvalid
                }
        default:
                err = errors.New("unsupported signature algorithm")
@@ -95,29 +152,18 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
        return
 }
 
-func (sd *SignedData) CheckSignatureFrom(parent *CerLoad) (err error) {
-       sig := sd.Sigs[0]
-       if sig.TBS.SID != parent.PKID {
-               err = errors.New("sid != parent pkid")
-               return
-       }
-       if len(sig.TBS.Hashes) == 0 {
-               err = errors.New("sig.tbs misses hash")
-               return
-       }
-       if sig.TBS.Hashes[sd.Hashes[0]] == nil {
-               err = errors.New("sig.tbs misses specified hash")
+func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) {
+       if len(sd.Sigs) != 1 {
+               err = errors.New("can verify only single signature")
                return
        }
-       hashNew, ok := HashToNew[sd.Hashes[0]]
-       if !ok {
-               err = errors.New("unsupported hash")
+       if !parent.CanSign() {
+               err = errors.New("parent can not sign")
                return
        }
-       hasher := hashNew()
-       hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(sd.Load.V)))
-       if !bytes.Equal(sig.TBS.Hashes[sd.Hashes[0]], hasher.Sum(nil)) {
-               err = errors.New("hash differs")
+       sig := sd.Sigs[0]
+       if sig.TBS.SID != parent.Pub[0].Id {
+               err = errors.New("sid != parent pub id")
                return
        }
        tbs := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
@@ -127,61 +173,51 @@ func (sd *SignedData) CheckSignatureFrom(parent *CerLoad) (err error) {
        )
 }
 
-func (sd *SignedData) PrehashedSignWith(parent *CerLoad, prv crypto.Signer) (err error) {
-       var hashAlgo string
-       var hashNew func() hash.Hash
-       switch parent.Pub.A {
-       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D, AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
-               hashAlgo = AlgoStreebog256
-               hashNew = gost34112012256.New
-       default:
-               return errors.New("unknown hash algorithm for public key")
-       }
-       hasher := hashNew()
-       hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(sd.Load.V)))
-       sig := Sig{TBS: SigTBS{
-               SID:    parent.PKID,
-               Hashes: map[string][]byte{hashAlgo: hasher.Sum(nil)},
-       }}
-       sdTBS := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
-       hasher = HasherByKeyAlgo(parent.Pub.A)
-       hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)))
-       sig.Sign.A = parent.Pub.A
-       sig.Sign.V, err = prv.Sign(rand.Reader, hasher.Sum(nil), nil)
-       if err != nil {
-               return
+func (sd *SignedData) CerLoad() *CerLoad {
+       l, ok := sd.Load.V.(CerLoad)
+       if ok {
+               return &l
        }
-       sd.Hashes = append(sd.Hashes, hashAlgo)
-       sd.Sigs = append(sd.Sigs, sig)
-       return
+       return nil
 }
 
-func CerParse(data []byte) (sd *SignedData, tail []byte, err error) {
-       var s SignedData
-       tail, err = gyac.DecodeToStruct(&s, data)
-       if err != nil {
-               return
+func (sd *SignedData) CerVerify(cers []*SignedData, t time.Time) (err error) {
+       {
+               exp := *(sd.Sigs[0].TBS.Exp)
+               if t.Before(exp[0]) || t.Equal(exp[0]) {
+                       err = errors.New("cer is not active")
+                       return
+               }
+               if t.After(exp[1]) || t.Equal(exp[1]) {
+                       err = errors.New("cer is expired")
+                       return
+               }
        }
-       sd = &s
-       if sd.Load.T != "cer" {
-               err = errors.New("SignedData: not \"cer\" type")
-               return
+       sid := sd.Sigs[0].TBS.SID
+       if sid == sd.CerLoad().Pub[0].Id {
+               return sd.CerCheckSignatureFrom(sd.CerLoad())
        }
-       if len(sd.Sigs) != 1 {
-               err = errors.New("SignedData: wrong number of sigs")
-               return
+       idToCer := make(map[uuid.UUID]*SignedData, len(cers))
+       for _, cer := range cers {
+               cerLoad := cer.CerLoad()
+               if !cerLoad.CanSign() {
+                       err = errors.New("cer can not sign")
+                       return
+               }
+               if _, ok := (*cerLoad.KU)[KUCA]; !ok {
+                       err = errors.New("cer can not ca")
+                       return
+               }
+               idToCer[cerLoad.Pub[0].Id] = cer
        }
-       if len(sd.Hashes) != 1 {
-               err = errors.New("SignedData: wrong number of hashes")
+       signer := idToCer[sid]
+       if signer == nil {
+               err = fmt.Errorf("no cer found for sid: %v", sd.Sigs[0].TBS.SID)
                return
        }
-       {
-               var load CerLoad
-               err = gyac.MapToStruct(&load, sd.Load.V.(map[string]any))
-               if err != nil {
-                       return
-               }
-               sd.Load.V = &load
+       err = sd.CerCheckSignatureFrom(signer.CerLoad())
+       if err != nil {
+               return
        }
-       return
+       return signer.CerVerify(cers, t)
 }
index a4ea1ca58b85d9603baa8bf496b8f5023d53e561c44aa676479d67be34f1912f..0c936754a2f89ea900e48b2644433574d05482de3d811f17733643403f9d5d08 100755 (executable)
@@ -6,32 +6,39 @@ test_description="Check that basic functionality works"
 
 TMPDIR=${TMPDIR:-/tmp}
 
-subj='{"CN": "CA", "C": "RU"}'
+subj="-subj CN=CA -subj C=RU"
 test_expect_success "CA generation" "yacertool \
     -algo gost3410-512C \
-    -ku ca \
-    -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
-    -subj '$subj'"
-
+    -ku ca -ku sig $subj \
+    -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer"
 test_expect_success "CA regeneration" "yacertool \
     -algo gost3410-512C \
-    -ku ca \
+    -ku ca -ku sig $subj \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
-    -reuse-key \
-    -subj '$subj'"
+    -reuse-key"
 test_expect_success "CA self-signature" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/ca.cer \
     -verify"
 
-subj='{"CN": "EE", "C": "RU"}'
+subj="-subj CN=SubCA -subj C=RU"
+test_expect_success "SubCA generation" "yacertool \
+    -ku ca -ku sig $subj \
+    -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer \
+    -ca-cer $TMPDIR/ca.cer -ca-prv $TMPDIR/ca.prv"
+test_expect_success "SubCA signature" "yacertool \
+    -ca-cer $TMPDIR/ca.cer \
+    -cer $TMPDIR/subca.cer \
+    -verify"
+
+subj="-subj CN=EE -subj C=RU"
 test_expect_success "EE generation" "yacertool \
-    -algo gost3410-256A \
-    -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer \
-    -prv $TMPDIR/ee.prv -cer $TMPDIR/ee.cer \
-    -subj '$subj'"
+    -algo gost3410-256A $subj \
+    -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
+    -prv $TMPDIR/ee.prv -cer $TMPDIR/ee.cer"
 test_expect_success "EE chain" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
+    -ca-cer $TMPDIR/subca.cer \
     -cer $TMPDIR/ee.cer \
     -verify"
 
index 5cd371a9955323132d6aacaec803ada7ca8a70c81ffc907bd530c0fff4fdc693..a32fb3cacb88649f92d40511c650e24c8a2ccd669e91a7527e703054e1ff2648 100644 (file)
@@ -3,35 +3,49 @@ package main
 import (
        "crypto"
        "crypto/rand"
-       "encoding/json"
+       "errors"
        "flag"
-       "fmt"
        "io"
        "log"
        "os"
-       "sort"
+       "strings"
        "time"
 
        "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/yac/gyac"
        "go.cypherpunks.su/yac/gyac/yacpki"
+       "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
-func MustReadFile(p string) []byte {
-       data, err := os.ReadFile(p)
-       if err != nil {
-               panic(fmt.Errorf("read %s: %v", p, err))
-       }
-       return data
-}
-
 func main() {
-       kuMap := make(map[string]struct{})
+       ku := make(map[string]*struct{})
+       subj := make(map[string]string)
        flag.Func(
                "ku",
-               "Optional key usage, may be specified multiple times",
+               "Optional key usage, can be specified multiple times",
+               func(v string) error {
+                       ku[v] = nil
+                       return nil
+               },
+       )
+       flag.Func(
+               "subj",
+               "Part of subject, key=value, can be specified multiple times",
+               func(v string) error {
+                       s := strings.SplitN(v, "=", 2)
+                       if len(s) != 2 {
+                               return errors.New("invalid key=value")
+                       }
+                       subj[s[0]] = s[1]
+                       return nil
+               },
+       )
+       var issuingCers []string
+       flag.Func(
+               "ca-cer",
+               "Add CA certificate to the chain",
                func(v string) error {
-                       kuMap[v] = struct{}{}
+                       issuingCers = append(issuingCers, v)
                        return nil
                },
        )
@@ -39,11 +53,7 @@ func main() {
                "Optional notBefore, \"2006-01-02 15:04:05\" format")
        lifetime := flag.Uint("lifetime", 365,
                "Lifetime of the certificate, days")
-       subjRaw := flag.String("subj", `{"CN": "test"}`,
-               "JSON map of the subject")
        algo := flag.String("algo", "gost3410-256A", "Public key algorithm")
-       issuingCer := flag.String("ca-cer", "",
-               "Path to certificate file for issuing with")
        issuingPrv := flag.String("ca-prv", "",
                "Path to private key file for issuing with")
        reuseKey := flag.Bool("reuse-key", false,
@@ -59,19 +69,11 @@ func main() {
                log.Fatal("no -cer is set")
        }
 
-       var ku []string
-       for k := range kuMap {
-               ku = append(ku, k)
-       }
-       kuMap = nil
-       sort.Sort(gyac.ByLenFirst(ku))
-
-       var subj map[string]string
-       err := json.Unmarshal([]byte(*subjRaw), &subj)
-       if err != nil {
-               log.Fatalln("while parsing -subj:", err)
+       if !*verify && len(subj) == 0 {
+               log.Fatal("no -subj is set")
        }
 
+       var err error
        var since time.Time
        if *sinceRaw == "" {
                since = time.Now().UTC().Truncate(time.Second)
@@ -84,43 +86,35 @@ func main() {
        till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
 
        var caPrv *gost3410.PrivateKey
-       var caCerLoad *yacpki.CerLoad
-       if *issuingCer != "" {
+       var caCers []*yacpki.SignedData
+       for _, issuingCer := range issuingCers {
                var sd *yacpki.SignedData
-               sd, _, err = yacpki.CerParse(MustReadFile(*issuingCer))
+               sd, _, err = yacpki.CerParse(utils.MustReadFile(issuingCer))
                if err != nil {
                        log.Fatal(err)
                }
-               caCerLoad = sd.Load.V.(*yacpki.CerLoad)
-               if !*verify {
-                       if *issuingPrv == "" {
-                               log.Fatal("no -issuing-key is set")
-                       }
-                       var signer crypto.Signer
-                       signer, err = yacpki.PrvParse(MustReadFile(*issuingPrv))
-                       if err != nil {
-                               log.Fatal(err)
-                       }
-                       caPrv = signer.(*gost3410.PrivateKey)
+               caCers = append(caCers, sd)
+       }
+       if len(caCers) > 0 && !*verify {
+               if *issuingPrv == "" {
+                       log.Fatal("no -ca-key is set")
+               }
+               var signer crypto.Signer
+               signer, err = yacpki.PrvParse(utils.MustReadFile(*issuingPrv))
+               if err != nil {
+                       log.Fatal(err)
                }
+               caPrv = signer.(*gost3410.PrivateKey)
        }
 
        if *verify {
                var sd *yacpki.SignedData
-               sd, _, err = yacpki.CerParse(MustReadFile(*cerPath))
+               sd, _, err = yacpki.CerParse(utils.MustReadFile(*cerPath))
                if err != nil {
                        log.Fatal(err)
                }
-               cerLoad := sd.Load.V.(*yacpki.CerLoad)
-               sig := sd.Sigs[0]
-               if sig.TBS.SID != cerLoad.PKID && !caCerLoad.HasCA() {
-                       log.Fatal("no \"ca\" KU met in CA")
-               }
-               err = sd.CheckSignatureFrom(caCerLoad)
+               err = sd.CerVerify(caCers, time.Now().UTC())
                if err != nil {
-                       if err == yacpki.SigInvalid {
-                               os.Exit(1)
-                       }
                        log.Fatal(err)
                }
                return
@@ -137,7 +131,7 @@ func main() {
        var prv *gost3410.PrivateKey
        if *reuseKey {
                var signer crypto.Signer
-               signer, err = yacpki.PrvParse(MustReadFile(*prvPath))
+               signer, err = yacpki.PrvParse(utils.MustReadFile(*prvPath))
                if err != nil {
                        log.Fatal(err)
                }
@@ -166,26 +160,28 @@ func main() {
        if err != nil {
                log.Fatal(err)
        }
-       cerLoad := yacpki.CerLoad{
-               KU:   ku,
-               Exp:  []time.Time{since, till},
-               Subj: subj,
-               Pub:  yacpki.AV{A: *algo, V: pub.RawBE()},
+       pubMap := yacpki.Pub{A: *algo, V: pub.RawBE()}
+       {
+               av := yacpki.AV{A: *algo, V: pub.RawBE()}
+               pubMap.Id = av.Id()
        }
-       cerLoad.PKID = yacpki.PKIDFromPub(&cerLoad.Pub)
+       cerLoad := yacpki.CerLoad{Subj: subj, Pub: []yacpki.Pub{pubMap}}
+       if len(ku) > 0 {
+               cerLoad.KU = &ku
+       }
+       var caCerLoad *yacpki.CerLoad
        if caPrv == nil {
                caPrv = prv
                caCerLoad = &cerLoad
        } else {
-               if !caCerLoad.HasCA() {
-                       log.Fatal("no \"ca\" KU met in CA")
-               }
+               caCerLoad = caCers[0].CerLoad()
        }
        sd := yacpki.SignedData{Load: yacpki.SignedDataLoad{T: "cer", V: cerLoad}}
-       err = sd.PrehashedSignWith(caCerLoad, caPrv)
+       err = sd.CerIssueWith(caCerLoad, caPrv, since, till)
        if err != nil {
                log.Fatal(err)
        }
+
        err = os.WriteFile(*cerPath, gyac.EncodeItem(nil, gyac.ItemFromGo(sd)), 0o666)
        if err != nil {
                log.Fatal(err)
diff --git a/gyac/yacpki/cmd/yacsdtool/main.go b/gyac/yacpki/cmd/yacsdtool/main.go
new file mode 100644 (file)
index 0000000..7473cb4
--- /dev/null
@@ -0,0 +1,106 @@
+package main
+
+import (
+       "bufio"
+       "bytes"
+       "crypto"
+       "flag"
+       "io"
+       "log"
+       "os"
+       "time"
+
+       "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki"
+       "go.cypherpunks.su/yac/gyac/yacpki/utils"
+)
+
+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")
+       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 provided -cer with -ca-cer")
+
+       flag.Parse()
+       log.SetFlags(log.Lshortfile)
+
+       if *cerPath == "" {
+               log.Fatal("no -cer is set")
+       }
+       cer, _, err := yacpki.CerParse(utils.MustReadFile(*cerPath))
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       var signer crypto.Signer
+       if !*verify {
+               if *prvPath == "" {
+                       log.Fatal("no -prv is set")
+               }
+               signer, err = yacpki.PrvParse(utils.MustReadFile(*prvPath))
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+
+       hashNew := yacpki.HashToNew[*hashAlgo]
+       if hashNew == nil {
+               log.Fatal("unknown -hash specified")
+       }
+       hasher := hashNew()
+       _, err = io.Copy(hasher, bufio.NewReader(os.Stdin))
+       if err != nil {
+               log.Fatal(err)
+       }
+       if *verify {
+               var sd *yacpki.SignedData
+               sd, _, err = yacpki.SignedDataParse(utils.MustReadFile(*sdPath))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               if len(sd.Sigs) == 0 {
+                       log.Fatal("no sigs")
+               }
+               sig := sd.Sigs[0]
+               if sd.Hashes == nil || sig.TBS.Hashes == nil {
+                       log.Fatal("no hashes")
+               }
+               hashes := *sig.TBS.Hashes
+               hashExpect := hashes[*hashAlgo]
+               hashGot := hasher.Sum(nil)
+               if hashExpect == nil || !bytes.Equal(hashExpect, hashGot) {
+                       log.Fatal("hash mismatch")
+               }
+               signer := cer.CerLoad()
+               if !signer.CanSign() {
+                       log.Fatal("cer can not sign")
+               }
+               if sig.Sign.A != signer.Pub[0].A {
+                       log.Fatal("differing signature algorithms")
+               }
+               err = sd.CerCheckSignatureFrom(signer)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       } else {
+               var sd yacpki.SignedData
+               sd.Load.T = *typ
+               sdHashes := []string{*hashAlgo}
+               sd.Hashes = &sdHashes
+               sigHashes := map[string][]byte{*hashAlgo: hasher.Sum(nil)}
+               when := time.Now().UTC().Truncate(1000 * time.Microsecond)
+               err = sd.SignWith(cer.CerLoad(), signer, yacpki.SigTBS{
+                       Hashes: &sigHashes,
+                       When:   &when,
+               })
+               if err != nil {
+                       log.Fatal(err)
+               }
+               err = os.WriteFile(*sdPath, gyac.EncodeItem(nil, gyac.ItemFromGo(sd)), 0o666)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+}
diff --git a/gyac/yacpki/signed-data.go b/gyac/yacpki/signed-data.go
new file mode 100644 (file)
index 0000000..7a69ff5
--- /dev/null
@@ -0,0 +1,143 @@
+package yacpki
+
+import (
+       "crypto"
+       "crypto/rand"
+       "errors"
+       "time"
+
+       "github.com/google/uuid"
+       "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki/utils"
+)
+
+type SignedDataLoad struct {
+       V any    `yac:"v,omitempty"`
+       T string `yac:"t"`
+}
+
+type SigTBS struct {
+       Hashes *map[string][]byte `yac:"hash,omitempty"`
+       CID    *uuid.UUID         `yac:"cid,omitempty"`
+       Exp    *[]time.Time       `yac:"exp,omitempty"`
+       When   *time.Time         `yac:"when,omitempty"`
+       SID    uuid.UUID          `yac:"sid"`
+}
+
+type Sig struct {
+       TBS    SigTBS    `yac:"tbs,omitempty"`
+       CerLoc *[]string `yac:"cer-loc,omitempty"`
+       Sign   AV        `yac:"sign"`
+}
+
+type SignedDataTBS struct {
+       V   any    `yac:"v"`
+       T   string `yac:"t"`
+       TBS SigTBS `yac:"tbs"`
+}
+
+type SignedData struct {
+       Hashes *[]string      `yac:"hash,omitempty"`
+       Cers   *[]*SignedData `yac:"certs,omitempty"`
+       Load   SignedDataLoad `yac:"load"`
+       Sigs   []*Sig         `yac:"sigs"`
+}
+
+func SignedDataParse(data []byte) (sd *SignedData, tail []byte, err error) {
+       var item *gyac.Item
+       item, tail, err = gyac.DecodeItem(data)
+       if err != nil {
+               return
+       }
+       sd, err = SignedDataParseItem(item)
+       return
+}
+
+func SignedDataParseItem(item *gyac.Item) (sd *SignedData, err error) {
+       if item.Typ() != gyac.ItemMap {
+               err = errors.New("SignedDataParse: non-map")
+               return
+       }
+       var _sd SignedData
+       err = gyac.MapToStruct(&_sd, item.ToGo().(map[string]any))
+       if err != nil {
+               return
+       }
+       sd = &_sd
+
+       if sd.Hashes != nil && len(*sd.Hashes) == 0 {
+               err = errors.New("SignedDataParse: empty /hash")
+               return
+       }
+       if sd.Cers != nil {
+               if len(*sd.Cers) == 0 {
+                       err = errors.New("SignedDataParse: empty /certs")
+                       return
+               }
+               for _, cer := range *sd.Cers {
+                       err = cer.CerParse()
+                       if err != nil {
+                               return
+                       }
+               }
+       }
+       for _, sig := range sd.Sigs {
+               if sig.CerLoc != nil && len(*sig.CerLoc) == 0 {
+                       err = errors.New("SignedDataParse: empty cer-loc")
+                       return
+               }
+               if sig.TBS.Hashes != nil && len(*sig.TBS.Hashes) == 0 {
+                       err = errors.New("SignedDataParse: empty hash")
+                       return
+               }
+               if sig.TBS.Exp != nil {
+                       if len(*sig.TBS.Exp) != 2 {
+                               err = errors.New("SignedDataParse: wrong exp len")
+                               return
+                       }
+                       for _, t := range *sig.TBS.Exp {
+                               if t.Nanosecond() != 0 {
+                                       err = errors.New("SignedDataParse: exp with nanoseconds")
+                                       return
+                               }
+                       }
+               }
+               if sd.Hashes != nil {
+                       if sig.TBS.Hashes == nil {
+                               err = errors.New("SignedDataParse: /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("SignedDataParse: /sigs: no hash")
+                               return
+                       }
+               }
+       }
+       return
+}
+
+func (sd *SignedData) SignWith(parent *CerLoad, prv crypto.Signer, sigTBS SigTBS) error {
+       if !parent.CanSign() {
+               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}
+       sig := Sig{TBS: sigTBS}
+       hasher := HasherByKeyAlgo(parent.Pub[0].A)
+       utils.MustWrite(hasher, gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)))
+       sig.Sign.A = parent.Pub[0].A
+       var err error
+       sig.Sign.V, err = prv.Sign(rand.Reader, hasher.Sum(nil), nil)
+       if err != nil {
+               return err
+       }
+       sd.Sigs = append(sd.Sigs, &sig)
+       return nil
+}
diff --git a/gyac/yacpki/utils/utils.go b/gyac/yacpki/utils/utils.go
new file mode 100644 (file)
index 0000000..5390719
--- /dev/null
@@ -0,0 +1,25 @@
+package utils
+
+import (
+       "fmt"
+       "io"
+       "os"
+)
+
+func MustWrite(w io.Writer, data []byte) {
+       n, err := w.Write(data)
+       if err != nil {
+               panic(err)
+       }
+       if n != len(data) {
+               panic("not full write")
+       }
+}
+
+func MustReadFile(p string) []byte {
+       data, err := os.ReadFile(p)
+       if err != nil {
+               panic(fmt.Errorf("read %s: %v", p, err))
+       }
+       return data
+}
index 83e05154d30be767eda1950a5cb25d8b9d0c8c30e8b6fc88239e1b61a332ef3f..801f0e06822540ca49686da0b4eb0cc0bfd27d504cf7f6bcbd264b7ab24430f1 100644 (file)
@@ -1,15 +1,12 @@
 ai = text ; algorithm identifier
 av = {a: ai, v: bytes}
 
-certificate-load = {
-    ? ku: [+ ku],
-    exp: validity,
-    pub: av,
+cer-load = {
+    ? ku: {+ ku => nil},
+    pub: [+ {av, id: uuid}],
     sub: {text => text}, ; subject
     ? crit: [+ {t: text, * text => any}],
-    pkid: uuid, ; public key identifier
     * text => any
 }
 
-ku = "ca" / "dh" / "sig" / text
-validity = [since: tai64, till: tai64]
+ku = "ca" / "sig" / "app-name" / text
diff --git a/spec/format/cer-sig-tbs.cddl b/spec/format/cer-sig-tbs.cddl
new file mode 100644 (file)
index 0000000..2034d50
--- /dev/null
@@ -0,0 +1,8 @@
+cer-sig-tbs = {
+    sid: uuid, ; signer's public key id
+    cid: uuid, ; certificate's id
+    exp: validity,
+    * text => any
+}
+
+validity = [since: tai64, till: tai64]
index e630845d9e85b019ccdd3d4cec2a63f953506d16b5ba538aaade2c0332993077..843d38854423b56130778b6260e8966d9d26686e293b48e11e06378bcf36df73 100644 (file)
@@ -2,56 +2,91 @@
 @cindex cer
 @section cer format
 
-Certificate is the prehashed @ref{signed-data} structure with
-@code{/load/t} equals to @code{cer}. @code{/load/v} must contain:
+Certificate is the @ref{signed-data} structure. Its @code{/load/t}
+equals to @code{cer}. @code{/load/v} contains @code{cer-load}:
 
 @verbatiminclude format/cer-load.cddl
 
-@code{pkid} is a hash calculated over the @code{pub} field and used to
-form UUIDv4. But it may be formed another way, no limitations. Depending
-on @code{pub/a}, that may be different hash like Streebog-256 or
-SHAKE-128 (let's stop using SHA-2!).
+@table @code
 
-@code{sub} is a subject name. Its values are UTF-8 strings. Currently no
-constraints on what fields must be present.
+@item sub
+Subject is a map of arbitrary strings. Currently no constraints on what
+fields must be present. Each application and usage context defines it on
+his own. But you may mimic X.509's subject with keys like "CN", "C", "O"
+and similar ones.
 
-@code{exp}iration period @strong{must} contain TAI64 datetime, without
-nanoseconds part.
+@item pub
+Certificate may contain multiple public keys.
 
-@code{ku} (key usage) contains supposed usage contexts like being CA
-(@code{ca}), or using it solely for either signing (@code{sig}), or
-key-agreement (@code{dh}) purposes. It @strong{must} be absent if empty.
-Its items @strong{must} be length-first sorted (like MAP) and unique.
-Possibly some additional usages like @code{email}, @code{ike},
-@code{codeSign} may be thought out.
+That is @strong{solely} intended for tasks requiring more than single
+key usage. For example @url{http://www.nncpgo.org, NNCP} uses one
+curve25519 for (DH) encryption, one curve25519 for online authentication
+and one ed25519 for signing purposes. All those three keys are used
+together. That certificate's key usage field must contain something like
+"nncp".
 
-Optional @code{crit} list may contain other critical extensions.
-Non-critical extensions may be placed just inside the map itself.
-It @strong{must} be absent if empty. Extension is a map with expected
-@code{t}ype field containing the identifier of the extension. Other
-extension's keys are defined by its type.
+If your keypair is intended for general purposes like signing of
+arbitrary data, then single public key @strong{should} be used, with a
+key usage like "sig".
 
-There @strong{must} be single signature and single hash used during
-prehashing.
+Each public key contain the key itself, its algorithm identifier and key
+identifier, that @strong{should} be generated as an UUIDv4 based on the
+hash of the key.
+
+@item ku
+Intended public key usage. Certificate @strong{must} be signed with the
+key having "ca" key usage, unless it is self-signed.
+Application-specific example with multiple public keys is described
+above.
+
+It is a map with NIL values, to force deterministic encoding of the
+list. It @strong{must} be absent if empty.
+
+@item crit
+Optional list of critical (in terms of X.509) extensions. Non-critical
+ones may be placed outside that map, directly in @code{cer-load}. It
+@strong{must} be absent if empty.
+
+Each extension has required "t" field with specified extension type. All
+other values are extension-specific.
+
+@end table
+
+signed-data's sig-tbs @strong{must} contain additional fields:
+
+@verbatiminclude format/cer-sig-tbs.cddl
+
+@table @code
+
+@item sid
+Signing public key identifier.
+
+@item cid
+Certificate's unique identifier. UUIDv7 is a good choice. But it may be
+UUIDv4, or any desired method of generation.
+
+@item exp
+Expiration period of the certificate. It @strong{must} contain TAI64
+datetime (no nanoseconds).
+
+@end table
 
 Example minimal certificate may look like:
 
 @verbatim
 {
-    "hash": ["streebog256"],
     "load": {
         "t": "cer",
         "v": {
-            "exp": [TAI64, TAI64],
-            "pub": {"a": "gost3410-256A", "v": 'pubkey'},
+            "pub": [{"a": "gost3410-256A", "v": 'pubkey', "id": UUID(hash(pub))}],
             "sub": {"CN": "Test", "O": "Testers"},
-            "pkid": UUID(hash(pub)),
         },
     },
     "sigs": [{
         "tbs": {
+            "cid": UUID(certificate's id),
             "sid": UUID(signer's pkid),
-            "hash": {"streebog256": 'hash value'},
+            "exp": [TAI64, TAI64],
         },
         "sign": {"a": "gost3410-256A", "v": 'signature'},
     }],
@@ -62,5 +97,6 @@ Example minimal certificate may look like:
 @subsection cer with GOST R 34.10-2012
 
 Same rules of serialisation must be used as with
-@ref{signed-data-gost3410, signed-data-gost3410}. @code{pkid} and
-@code{cid} should be calculated using Streebog-256 hash.
+@ref{signed-data-gost3410, signed-data-gost3410}. Public key's
+identifier and and @code{cid} should be calculated using Streebog-256
+hash.
index f39966d7baee09aeb783020bccfe1baeb0e8dac17520849df3653f9daedeb0f8..ad1618975c57352f320d166737755291131d8fa98c091c741e0a9f283d5d3b4e 100644 (file)
@@ -2,7 +2,7 @@
 @cindex hashed-data
 @section hashed-data format
 
-Integrity protected container, analogue to CMS'es DigestedData structure.
+Integrity protected container, CMS'es DigestedData analogue.
 
 @verbatiminclude format/hashed-data.cddl
 
@@ -17,3 +17,15 @@ converted from BIN to BLOB.
 
 @code{/hash} contains the hash values for all corresponding @code{/a}
 algorithms.
+
+@node hashed-data-gost3411
+@subsection hashed-data with GOST R 34.11-2012
+
+Streebog must be big-endian serialised.
+
+Following algorithm identifiers are acceptable:
+
+@verbatim
+streebog256
+streebog512
+@end verbatim
index 30c292e05e9d2fb7d3503c18463396f545be654dad860b16350e058f082661ef..e441adff7e720b8a398773103bcf84eebcea7c585ffbe5a5e534839c7c5fe12d 100644 (file)
@@ -1,4 +1,4 @@
-private-key = {
-    a: text, ; algorithm identifier
-    v: bytes, ; private key's value
-}
+ai = text ; algorithm identifier
+av = {a: ai, v: bytes}
+
+private-key = av
index c55f442888748fb59030b710fb8f82d170defccbb3f8b5622e14b38a012dc85d..64199c5ca5443430157541fd7990506a98980e3a227d6e8faaf4bdebfb858cad 100644 (file)
@@ -10,3 +10,15 @@ Private key is stored in trivial map:
 @subsection private-key with GOST R 34.10-2012
 
 Big-endian private key representation must be used.
+
+Following algorithm identifiers are acceptable:
+
+@verbatim
+gost3410-256A
+gost3410-256B
+gost3410-256C
+gost3410-256D
+gost3410-512A
+gost3410-512B
+gost3410-512C
+@end verbatim
index 231733b95380a336ba1395ee4a50085112b0d3db29f7663c80de7f9fea864ebf..f6fb011002e67bc0cda771ac8f42f37d1c5fd1b94f298f336a4ef7184096fc57 100644 (file)
@@ -1,5 +1,5 @@
 sig-tbs = {
     t: text, ; = /load/t
-    v: '' / any, ; empty string if prehashed, /load/v otherwise
+    v: nil / any,
     tbs: map, ; = /sigs/?/tbs
 }
index 0d75261dc453d2536072972a803f153800dd1d83fc52bc3baa60372c33b87026..356f91354890b11d32de8e70a9b20ed5cc2691436238253d7c0c485fe54f2250 100644 (file)
@@ -11,18 +11,20 @@ signed-data = {
     ? certs: [+ cer],
 }
 
-cer = signed-data ; with /load/t = cer
+cer = signed-data ; with /load/t = cer, /load/v = cer-load
 
 sig = {
-    tbs: {
-        sid: uuid, ; signer's public key id
-        ? hash: {ai => bytes}, ; when using prehashing
-        ? when: tai64 / tai64n,
-        * text => any
-    },
+    tbs: sig-tbs,
     sign: av,
     ? cer-loc: [+ url],
     * text => any
 }
 
 url = text
+
+sig-tbs = {
+    sid: uuid, ; signer's public key id
+    ? hash: {ai => bytes}, ; when using prehashing
+    ? when: tai64 / tai64n,
+    * text => any
+}
index 92367cdb8ec3bde331610ae64dbb27e9f9e02713c2fcf31eb30fbf4664ae3f1d..7583d90d61ed0da00f2a6699bd7048e914caa97e224c024c4e689b3655fd5c93 100644 (file)
@@ -37,3 +37,10 @@ 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
 in @code{BE(X)||BE(Y)} format. Signature is in @code{BE(S)||BE(R)}
 format.
+
+Following algorithm identifiers are acceptable for the hash:
+
+@verbatim
+streebog256
+streebog512
+@end verbatim