]> Cypherpunks repositories - keks.git/commitdiff
gyac/yacpki
authorSergey Matveev <stargrave@stargrave.org>
Mon, 7 Oct 2024 12:52:31 +0000 (15:52 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 8 Oct 2024 12:56:22 +0000 (15:56 +0300)
12 files changed:
gyac/cmd/certool/cer.go [deleted file]
gyac/cmd/certool/main.go [deleted file]
gyac/cmd/yacertool/basic.t [moved from gyac/cmd/certool/basic.t with 74% similarity]
gyac/cmd/yacertool/go.mod [moved from gyac/cmd/certool/go.mod with 63% similarity]
gyac/cmd/yacertool/go.sum [moved from gyac/cmd/certool/go.sum with 100% similarity]
gyac/cmd/yacertool/main.go [new file with mode: 0644]
gyac/reflect.go
gyac/yacpki/cer.go [new file with mode: 0644]
gyac/yacpki/crypto.go [new file with mode: 0644]
gyac/yacpki/go.mod [new file with mode: 0644]
gyac/yacpki/go.sum [new file with mode: 0644]
gyac/yacpki/prv.go [moved from gyac/cmd/certool/prv.go with 83% similarity]

diff --git a/gyac/cmd/certool/cer.go b/gyac/cmd/certool/cer.go
deleted file mode 100644 (file)
index 4ebc352..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package main
-
-import (
-       "errors"
-       "os"
-       "time"
-
-       "github.com/google/uuid"
-       "go.cypherpunks.su/yac/gyac"
-)
-
-type AV struct {
-       A string `yac:"a"`
-       V []byte `yac:"v"`
-}
-
-type CerTBS 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"`
-       KID  uuid.UUID         `yac:"kid"`
-}
-
-func (tbs *CerTBS) HasCA() (hasCA bool) {
-       for _, ku := range tbs.KU {
-               if ku == "ca" {
-                       hasCA = true
-               }
-       }
-       return
-}
-
-type SignedDataLoad struct {
-       V any    `yac:"v"`
-       T string `yac:"t"`
-}
-
-type SignedDataTBS struct {
-       V    any            `yac:"v"`
-       Load map[string]any `yac:"load"`
-       T    string         `yac:"t"`
-       KID  uuid.UUID      `yac:"kid"`
-}
-
-type Sig struct {
-       Load   map[string]any `yac:"load,omitempty"`
-       CerLoc []string       `yac:"cer-loc,omitempty"`
-       Sign   AV             `yac:"sign"`
-       KID    uuid.UUID      `yac:"kid"`
-}
-
-type SignedData struct {
-       Hashes []string       `yac:"hash,omitempty"`
-       Load   SignedDataLoad `yac:"load"`
-       Sigs   []Sig          `yac:"sigs"`
-       Cers   []SignedData   `yac:"certs,omitempty"`
-}
-
-func loadCerFromFile(pth string) (*SignedData, error) {
-       data, err := os.ReadFile(pth)
-       if err != nil {
-               return nil, err
-       }
-       var sd SignedData
-       err = gyac.DecodeToStruct(&sd, data)
-       if err != nil {
-               return nil, err
-       }
-       if sd.Load.T != "cer" {
-               err = errors.New("non \"cer\" in CA SignedData")
-       }
-       if len(sd.Sigs) == 0 {
-               err = errors.New("no signatures present")
-       }
-       return &sd, err
-}
diff --git a/gyac/cmd/certool/main.go b/gyac/cmd/certool/main.go
deleted file mode 100644 (file)
index 1a3cc65..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "crypto/rand"
-       "encoding/json"
-       "flag"
-       "hash"
-       "io"
-       "log"
-       "os"
-       "sort"
-       "time"
-
-       "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"
-)
-
-const (
-       AlgoStreebog256  = "streebog256"
-       AlgoGOST3410256A = "gost3410-256A"
-       AlgoGOST3410256B = "gost3410-256B"
-       AlgoGOST3410256C = "gost3410-256C"
-       AlgoGOST3410256D = "gost3410-256D"
-       AlgoGOST3410512A = "gost3410-512A"
-       AlgoGOST3410512B = "gost3410-512B"
-       AlgoGOST3410512C = "gost3410-512C"
-)
-
-func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
-       switch name {
-       case AlgoGOST3410256A:
-               curve = gost3410.CurveIdtc26gost341012256paramSetA()
-       case AlgoGOST3410256B:
-               curve = gost3410.CurveIdtc26gost341012256paramSetB()
-       case AlgoGOST3410256C:
-               curve = gost3410.CurveIdtc26gost341012256paramSetC()
-       case AlgoGOST3410256D:
-               curve = gost3410.CurveIdtc26gost341012256paramSetD()
-       case AlgoGOST3410512A:
-               curve = gost3410.CurveIdtc26gost341012512paramSetA()
-       case AlgoGOST3410512B:
-               curve = gost3410.CurveIdtc26gost341012512paramSetB()
-       case AlgoGOST3410512C:
-               curve = gost3410.CurveIdtc26gost341012512paramSetC()
-       }
-       return
-}
-
-func HasherByKeyAlgo(a string) hash.Hash {
-       switch a {
-       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D:
-               return gost34112012256.New()
-       case AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
-               return gost34112012512.New()
-       default:
-               log.Fatal("unsupported CA algorithm")
-       }
-       return nil
-}
-
-func main() {
-       kuMap := make(map[string]struct{})
-       flag.Func(
-               "ku",
-               "Optional key usage, may be specified multiple times",
-               func(v string) error {
-                       kuMap[v] = struct{}{}
-                       return nil
-               },
-       )
-       sinceRaw := flag.String("since", "",
-               "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,
-               "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")
-
-       flag.Parse()
-       log.SetFlags(log.Lshortfile)
-
-       if *cerPath == "" {
-               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)
-       }
-
-       var since time.Time
-       if *sinceRaw == "" {
-               since = time.Now().UTC().Truncate(time.Second)
-       } else {
-               since, err = time.Parse(time.DateTime, *sinceRaw)
-               if err != nil {
-                       log.Fatalln("while parsing -since:", err)
-               }
-       }
-       till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
-
-       var caPrv *gost3410.PrivateKey
-       var caCerTBS CerTBS
-       if *issuingCer != "" {
-               var sd *SignedData
-               sd, err = loadCerFromFile(*issuingCer)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               err = gyac.MapToStruct(&caCerTBS, sd.Load.V.(map[string]any))
-               if err != nil {
-                       log.Fatal(err)
-               }
-               if !*verify {
-                       if *issuingPrv == "" {
-                               log.Fatal("no -issuing-key is set")
-                       }
-                       caPrv, err = loadPrvFromFile(*issuingPrv)
-                       if err != nil {
-                               log.Fatal(err)
-                       }
-               }
-       }
-
-       if *verify {
-               var sd *SignedData
-               sd, err = loadCerFromFile(*cerPath)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               var tbs CerTBS
-               err = gyac.MapToStruct(&tbs, sd.Load.V.(map[string]any))
-               if err != nil {
-                       log.Fatal(err)
-               }
-               sig := sd.Sigs[0]
-               if sig.KID != caCerTBS.KID {
-                       log.Fatal("SKID != AKID")
-               }
-               if sig.KID != tbs.KID && !caCerTBS.HasCA() {
-                       log.Fatal("no \"ca\" KU met in CA")
-               }
-               sdTBS := SignedDataTBS{T: "cer", V: tbs, KID: sig.KID}
-               hasher := HasherByKeyAlgo(sig.Sign.A)
-               hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)))
-               var pub *gost3410.PublicKey
-               pub, err = gost3410.NewPublicKeyBE(
-                       GOST3410CurveByName(caCerTBS.Pub.A),
-                       caCerTBS.Pub.V,
-               )
-               if err != nil {
-                       log.Fatal(err)
-               }
-               var valid bool
-               valid, err = pub.VerifyDigest(hasher.Sum(nil), sig.Sign.V)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               if !valid {
-                       os.Exit(1)
-               }
-               return
-       }
-
-       if *prvPath == "" {
-               log.Fatal("no -prv is set")
-       }
-
-       curve := GOST3410CurveByName(*algo)
-       if curve == nil {
-               log.Fatal("unknown -algo specified")
-       }
-       var prv *gost3410.PrivateKey
-       if *reuseKey {
-               prv, err = loadPrvFromFile(*prvPath)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               if prv.C.Name != curve.Name {
-                       log.Fatal("-algo is not same with private key")
-               }
-       } else {
-               prvRaw := make([]byte, curve.PointSize())
-               if _, err = io.ReadFull(rand.Reader, prvRaw); err != nil {
-                       log.Fatal(err)
-               }
-               prv, err = gost3410.NewPrivateKeyBE(curve, prvRaw)
-               if err != nil {
-                       log.Fatal(err)
-               }
-               err = os.WriteFile(*prvPath, gyac.EncodeItem(nil,
-                       gyac.ItemFromGo(AV{A: *algo, V: prv.RawBE()})), 0o600)
-               if err != nil {
-                       log.Fatal(err)
-               }
-       }
-
-       var pub *gost3410.PublicKey
-       pub, err = prv.PublicKey()
-       if err != nil {
-               log.Fatal(err)
-       }
-       pubTBS := AV{A: *algo, V: pub.RawBE()}
-       var spki uuid.UUID
-       {
-               hasher := gost34112012256.New()
-               hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(pubTBS)))
-               spki, err = uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil)))
-               if err != nil {
-                       log.Fatal(err)
-               }
-       }
-       cerTBS := CerTBS{
-               KU:   ku,
-               Exp:  []time.Time{since, till},
-               KID:  spki,
-               Subj: subj,
-               Pub:  pubTBS,
-       }
-       if caPrv == nil {
-               caPrv = prv
-               caCerTBS = cerTBS
-       } else {
-               if !caCerTBS.HasCA() {
-                       log.Fatal("no \"ca\" KU met in CA")
-               }
-       }
-       sig := Sig{KID: caCerTBS.KID}
-       sdTBS := SignedDataTBS{T: "cer", V: cerTBS, KID: sig.KID}
-       {
-               hasher := HasherByKeyAlgo(caCerTBS.Pub.A)
-               hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)))
-               sig.Sign = AV{A: caCerTBS.Pub.A}
-               sig.Sign.V, err = caPrv.SignDigest(hasher.Sum(nil), rand.Reader)
-               if err != nil {
-                       log.Fatal(err)
-               }
-       }
-       cer := SignedData{Load: SignedDataLoad{T: "cer", V: cerTBS}, Sigs: []Sig{sig}}
-       err = os.WriteFile(*cerPath, gyac.EncodeItem(nil, gyac.ItemFromGo(cer)), 0o666)
-       if err != nil {
-               log.Fatal(err)
-       }
-}
similarity index 74%
rename from gyac/cmd/certool/basic.t
rename to gyac/cmd/yacertool/basic.t
index 9db0be1b6de8e8adb2b1919f4d9dd8dbcbb3fd2636625eb53a374d425966882f..a4ea1ca58b85d9603baa8bf496b8f5023d53e561c44aa676479d67be34f1912f 100755 (executable)
@@ -7,30 +7,30 @@ test_description="Check that basic functionality works"
 TMPDIR=${TMPDIR:-/tmp}
 
 subj='{"CN": "CA", "C": "RU"}'
-test_expect_success "CA generation" "certool \
+test_expect_success "CA generation" "yacertool \
     -algo gost3410-512C \
     -ku ca \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
     -subj '$subj'"
 
-test_expect_success "CA regeneration" "certool \
+test_expect_success "CA regeneration" "yacertool \
     -algo gost3410-512C \
     -ku ca \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
     -reuse-key \
     -subj '$subj'"
-test_expect_success "CA self-signature" "certool \
+test_expect_success "CA self-signature" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/ca.cer \
     -verify"
 
 subj='{"CN": "EE", "C": "RU"}'
-test_expect_success "EE generation" "certool \
+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'"
-test_expect_success "EE chain" "certool \
+test_expect_success "EE chain" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/ee.cer \
     -verify"
similarity index 63%
rename from gyac/cmd/certool/go.mod
rename to gyac/cmd/yacertool/go.mod
index c8dfebd3288aa5ce8c8e5a674f2ee1503136aceb9ce6fb0c03edb846da2e9f78..6c9c464021c608799568755e3230c67b049a4b198fa7ee8da870d9fe4f52bf6c 100644 (file)
@@ -1,4 +1,4 @@
-module go.cypherpunks.su/yac/gyac/cmd/certool
+module go.cypherpunks.su/yac/gyac/cmd/yacertool
 
 go 1.22
 
@@ -6,6 +6,7 @@ require (
        github.com/google/uuid v1.6.0
        go.cypherpunks.su/gogost/v6 v6.0.1
        go.cypherpunks.su/yac/gyac v0.0.0-00010101000000-000000000000
+       go.cypherpunks.su/yac/gyac/yacpki v0.0.0-00010101000000-000000000000
 )
 
 require (
@@ -14,3 +15,5 @@ require (
 )
 
 replace go.cypherpunks.su/yac/gyac => ../..
+
+replace go.cypherpunks.su/yac/gyac/yacpki => ../../yacpki
diff --git a/gyac/cmd/yacertool/main.go b/gyac/cmd/yacertool/main.go
new file mode 100644 (file)
index 0000000..ade183a
--- /dev/null
@@ -0,0 +1,193 @@
+package main
+
+import (
+       "crypto"
+       "crypto/rand"
+       "encoding/json"
+       "flag"
+       "fmt"
+       "io"
+       "log"
+       "os"
+       "sort"
+       "time"
+
+       "go.cypherpunks.su/gogost/v6/gost3410"
+       "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki"
+)
+
+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{})
+       flag.Func(
+               "ku",
+               "Optional key usage, may be specified multiple times",
+               func(v string) error {
+                       kuMap[v] = struct{}{}
+                       return nil
+               },
+       )
+       sinceRaw := flag.String("since", "",
+               "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,
+               "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")
+
+       flag.Parse()
+       log.SetFlags(log.Lshortfile)
+
+       if *cerPath == "" {
+               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)
+       }
+
+       var since time.Time
+       if *sinceRaw == "" {
+               since = time.Now().UTC().Truncate(time.Second)
+       } else {
+               since, err = time.Parse(time.DateTime, *sinceRaw)
+               if err != nil {
+                       log.Fatalln("while parsing -since:", err)
+               }
+       }
+       till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
+
+       var caPrv *gost3410.PrivateKey
+       var caCerLoad *yacpki.CerLoad
+       if *issuingCer != "" {
+               var sd *yacpki.SignedData
+               sd, _, err = yacpki.CerParse(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)
+               }
+       }
+
+       if *verify {
+               var sd *yacpki.SignedData
+               sd, _, err = yacpki.CerParse(MustReadFile(*cerPath))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               cerLoad := sd.Load.V.(*yacpki.CerLoad)
+               sig := sd.Sigs[0]
+               if sig.KID != cerLoad.KID && !caCerLoad.HasCA() {
+                       log.Fatal("no \"ca\" KU met in CA")
+               }
+               err = sd.CheckSignatureFrom(caCerLoad)
+               if err != nil {
+                       if err == yacpki.SigInvalid {
+                               os.Exit(1)
+                       }
+                       log.Fatal(err)
+               }
+               return
+       }
+
+       if *prvPath == "" {
+               log.Fatal("no -prv is set")
+       }
+
+       curve := yacpki.GOST3410CurveByName(*algo)
+       if curve == nil {
+               log.Fatal("unknown -algo specified")
+       }
+       var prv *gost3410.PrivateKey
+       if *reuseKey {
+               var signer crypto.Signer
+               signer, err = yacpki.PrvParse(MustReadFile(*prvPath))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               prv = signer.(*gost3410.PrivateKey)
+               if prv.C.Name != curve.Name {
+                       log.Fatal("-algo is not same with private key")
+               }
+       } else {
+               prvRaw := make([]byte, curve.PointSize())
+               if _, err = io.ReadFull(rand.Reader, prvRaw); err != nil {
+                       log.Fatal(err)
+               }
+               prv, err = gost3410.NewPrivateKeyBE(curve, prvRaw)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               err = os.WriteFile(*prvPath, gyac.EncodeItem(nil,
+                       gyac.ItemFromGo(yacpki.AV{A: *algo, V: prv.RawBE()})), 0o600)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+
+       var pub *gost3410.PublicKey
+       pub, err = prv.PublicKey()
+       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()},
+       }
+       cerLoad.KID = yacpki.KIDFromPub(&cerLoad.Pub)
+       if caPrv == nil {
+               caPrv = prv
+               caCerLoad = &cerLoad
+       } else {
+               if !caCerLoad.HasCA() {
+                       log.Fatal("no \"ca\" KU met in CA")
+               }
+       }
+       sd := yacpki.SignedData{Load: yacpki.SignedDataLoad{T: "cer", V: cerLoad}}
+       err = sd.SignWith(caCerLoad, caPrv)
+       if err != nil {
+               log.Fatal(err)
+       }
+       err = os.WriteFile(*cerPath, gyac.EncodeItem(nil, gyac.ItemFromGo(sd)), 0o666)
+       if err != nil {
+               log.Fatal(err)
+       }
+}
index b1be6ef9165b50d88cc40f695885a3783118572b2b5656a7b19a0514e1e01dff..d4f95148c8ea0731b0062d8432428588ba65d312efd8a9789784dfc671dd2e0a 100644 (file)
@@ -225,16 +225,16 @@ func MapToStruct(dst any, src map[string]any) error {
        return decoder.Decode(src)
 }
 
-func DecodeToStruct(dst any, raw []byte) error {
-       item, tail, err := DecodeItem(raw)
+func DecodeToStruct(dst any, raw []byte) (tail []byte, err error) {
+       var item *Item
+       item, tail, err = DecodeItem(raw)
        if err != nil {
-               return err
-       }
-       if len(tail) != 0 {
-               return errors.New("trailing data")
+               return
        }
        if ItemType(item.T) != ItemMap {
-               return errors.New("non-map")
+               err = errors.New("non-map")
+               return
        }
-       return MapToStruct(dst, item.ToGo().(map[string]any))
+       err = MapToStruct(dst, item.ToGo().(map[string]any))
+       return
 }
diff --git a/gyac/yacpki/cer.go b/gyac/yacpki/cer.go
new file mode 100644 (file)
index 0000000..6a40913
--- /dev/null
@@ -0,0 +1,147 @@
+package yacpki
+
+import (
+       "bytes"
+       "crypto"
+       "crypto/rand"
+       "errors"
+       "time"
+
+       "github.com/google/uuid"
+       "go.cypherpunks.su/gogost/v6/gost3410"
+       "go.cypherpunks.su/gogost/v6/gost34112012256"
+       "go.cypherpunks.su/yac/gyac"
+)
+
+type AV struct {
+       A string `yac:"a"`
+       V []byte `yac:"v"`
+}
+
+type SignedDataLoad struct {
+       V any    `yac:"v"`
+       T string `yac:"t"`
+}
+
+type SignedDataTBS struct {
+       V    any            `yac:"v"`
+       Load map[string]any `yac:"load"`
+       T    string         `yac:"t"`
+       KID  uuid.UUID      `yac:"kid"`
+}
+
+type Sig struct {
+       Load   map[string]any `yac:"load,omitempty"`
+       CerLoc []string       `yac:"cer-loc,omitempty"`
+       Sign   AV             `yac:"sign"`
+       KID    uuid.UUID      `yac:"kid"`
+}
+
+type SignedData struct {
+       Hashes []string       `yac:"hash,omitempty"`
+       Load   SignedDataLoad `yac:"load"`
+       Sigs   []Sig          `yac:"sigs"`
+       Cers   []SignedData   `yac:"certs,omitempty"`
+}
+
+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"`
+       KID  uuid.UUID         `yac:"kid"`
+}
+
+func (tbs *CerLoad) HasCA() (hasCA bool) {
+       for _, ku := range tbs.KU {
+               if ku == "ca" {
+                       hasCA = true
+               }
+       }
+       return
+}
+
+func KIDFromPub(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)))
+       if err != nil {
+               panic(err)
+       }
+       return
+}
+
+var SigInvalid = errors.New("signature is invalid")
+
+func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
+       switch cer.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)
+               if err != nil {
+                       return
+               }
+               hasher := HasherByKeyAlgo(cer.Pub.A)
+               hasher.Write(signed)
+               var valid bool
+               valid, err = pub.VerifyDigest(hasher.Sum(nil), signature)
+               if !valid {
+                       err = SigInvalid
+               }
+       default:
+               err = errors.New("unsupported signature algorithm")
+       }
+       return
+}
+
+func (sd *SignedData) CheckSignatureFrom(parent *CerLoad) (err error) {
+       sig := sd.Sigs[0]
+       if sig.KID != parent.KID {
+               err = errors.New("signer KID != parent KID")
+               return
+       }
+       tbs := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, KID: parent.KID}
+       return parent.CheckSignature(
+               gyac.EncodeItem(nil, gyac.ItemFromGo(tbs)),
+               sig.Sign.V,
+       )
+}
+
+func (sd *SignedData) SignWith(parent *CerLoad, prv crypto.Signer) (err error) {
+       tbs := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, KID: parent.KID}
+       hasher := HasherByKeyAlgo(parent.Pub.A)
+       hasher.Write(gyac.EncodeItem(nil, gyac.ItemFromGo(tbs)))
+       sig := Sig{KID: parent.KID, Sign: AV{A: parent.Pub.A}}
+       sig.Sign.V, err = prv.Sign(rand.Reader, hasher.Sum(nil), nil)
+       if err != nil {
+               return
+       }
+       sd.Sigs = append(sd.Sigs, sig)
+       return
+}
+
+func CerParse(data []byte) (sd *SignedData, tail []byte, err error) {
+       var s SignedData
+       tail, err = gyac.DecodeToStruct(&s, data)
+       if err != nil {
+               return
+       }
+       sd = &s
+       if sd.Load.T != "cer" {
+               err = errors.New("SignedData: not \"cer\" type")
+               return
+       }
+       if len(sd.Sigs) == 0 {
+               err = errors.New("SignedData: no \"sigs\"")
+               return
+       }
+       var tbs CerLoad
+       err = gyac.MapToStruct(&tbs, sd.Load.V.(map[string]any))
+       if err != nil {
+               return
+       }
+       sd.Load.V = &tbs
+       return
+}
diff --git a/gyac/yacpki/crypto.go b/gyac/yacpki/crypto.go
new file mode 100644 (file)
index 0000000..0507478
--- /dev/null
@@ -0,0 +1,53 @@
+package yacpki
+
+import (
+       "hash"
+       "log"
+
+       "go.cypherpunks.su/gogost/v6/gost3410"
+       "go.cypherpunks.su/gogost/v6/gost34112012256"
+       "go.cypherpunks.su/gogost/v6/gost34112012512"
+)
+
+const (
+       AlgoStreebog256  = "streebog256"
+       AlgoGOST3410256A = "gost3410-256A"
+       AlgoGOST3410256B = "gost3410-256B"
+       AlgoGOST3410256C = "gost3410-256C"
+       AlgoGOST3410256D = "gost3410-256D"
+       AlgoGOST3410512A = "gost3410-512A"
+       AlgoGOST3410512B = "gost3410-512B"
+       AlgoGOST3410512C = "gost3410-512C"
+)
+
+func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
+       switch name {
+       case AlgoGOST3410256A:
+               curve = gost3410.CurveIdtc26gost341012256paramSetA()
+       case AlgoGOST3410256B:
+               curve = gost3410.CurveIdtc26gost341012256paramSetB()
+       case AlgoGOST3410256C:
+               curve = gost3410.CurveIdtc26gost341012256paramSetC()
+       case AlgoGOST3410256D:
+               curve = gost3410.CurveIdtc26gost341012256paramSetD()
+       case AlgoGOST3410512A:
+               curve = gost3410.CurveIdtc26gost341012512paramSetA()
+       case AlgoGOST3410512B:
+               curve = gost3410.CurveIdtc26gost341012512paramSetB()
+       case AlgoGOST3410512C:
+               curve = gost3410.CurveIdtc26gost341012512paramSetC()
+       }
+       return
+}
+
+func HasherByKeyAlgo(a string) hash.Hash {
+       switch a {
+       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D:
+               return gost34112012256.New()
+       case AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
+               return gost34112012512.New()
+       default:
+               log.Fatal("unsupported CA algorithm")
+       }
+       return nil
+}
diff --git a/gyac/yacpki/go.mod b/gyac/yacpki/go.mod
new file mode 100644 (file)
index 0000000..71040d4
--- /dev/null
@@ -0,0 +1,15 @@
+module go.cypherpunks.su/yac/gyac/yacpki
+
+go 1.22
+
+require go.cypherpunks.su/tai64n/v3 v3.1.0 // indirect
+
+require (
+       github.com/google/uuid v1.6.0
+       go.cypherpunks.su/gogost/v6 v6.0.1
+       go.cypherpunks.su/yac/gyac v0.0.0-00010101000000-000000000000
+)
+
+require github.com/mitchellh/mapstructure v1.5.0 // indirect
+
+replace go.cypherpunks.su/yac/gyac => ..
diff --git a/gyac/yacpki/go.sum b/gyac/yacpki/go.sum
new file mode 100644 (file)
index 0000000..90b7e96
--- /dev/null
@@ -0,0 +1,10 @@
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+go.cypherpunks.su/gogost/v6 v6.0.1 h1:PFjBnUmfdbx7L5R6hRt/+ZgGwWx45wTIWezFSgmknrs=
+go.cypherpunks.su/gogost/v6 v6.0.1/go.mod h1:qJm0B7KJY4/OD5nYqL10kXY09dUwu2AfwSPu72Otngs=
+go.cypherpunks.su/tai64n/v3 v3.1.0 h1:cdGnanxA5/H3hc37BO9D3h/exChVNEvrPWjTT/kuwQ4=
+go.cypherpunks.su/tai64n/v3 v3.1.0/go.mod h1:zGDFuyiFKJk+iem8lyBaFeCm+MNMOn7RRWy456n1J78=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
similarity index 83%
rename from gyac/cmd/certool/prv.go
rename to gyac/yacpki/prv.go
index ac3cf3b0377ec96a4031ba156c3b752d21c7fbd09c7057e1add3fa850edbf712..17d01790b6a7a1c3221866411c302fbed17881f358ea335ea233af4f8cbe73ce 100644 (file)
@@ -1,22 +1,23 @@
-package main
+package yacpki
 
 import (
+       "crypto"
+       "errors"
        "fmt"
-       "os"
 
        "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/yac/gyac"
 )
 
-func loadPrvFromFile(pth string) (prv *gost3410.PrivateKey, err error) {
-       var data []byte
-       data, err = os.ReadFile(pth)
+func PrvParse(data []byte) (prv crypto.Signer, err error) {
+       var av AV
+       var tail []byte
+       tail, err = gyac.DecodeToStruct(&av, data)
        if err != nil {
                return
        }
-       var av AV
-       err = gyac.DecodeToStruct(&av, data)
-       if err != nil {
+       if len(tail) != 0 {
+               err = errors.New("trailing data")
                return
        }
        switch av.A {
@@ -50,7 +51,6 @@ func loadPrvFromFile(pth string) (prv *gost3410.PrivateKey, err error) {
                )
        default:
                err = fmt.Errorf("unknown private key algo: %s", av.A)
-               return
        }
        return
 }