From 42db0bce66d6efc0a86808da398390d3c75c6a93008267aa34875aaa1a5c6872 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 12 Dec 2024 16:00:58 +0300 Subject: [PATCH] Refactor and reorganise yacpki Mainly move cryptography-related code outside. --- gyac/yacpki/algo.go | 52 ++------------ gyac/yacpki/av.go | 39 +++++++++++ gyac/yacpki/cer.go | 89 +++++++++++------------- gyac/yacpki/cmd/yacertool/main.go | 98 ++++++++------------------- gyac/yacpki/cmd/yacsdtool/main.go | 10 +-- gyac/yacpki/doc.go | 2 + gyac/yacpki/ed25519-blake2b/algo.go | 3 + gyac/yacpki/ed25519-blake2b/kp.go | 21 ++++++ gyac/yacpki/ed25519-blake2b/prv.go | 19 ++++++ gyac/yacpki/ed25519-blake2b/verify.go | 16 +++++ gyac/yacpki/gost.go | 47 ------------- gyac/yacpki/gost/gost.go | 20 ++++++ gyac/yacpki/gost/kp.go | 30 ++++++++ gyac/yacpki/gost/signer.go | 64 +++++++++++++++++ gyac/yacpki/gost/verify.go | 30 ++++++++ gyac/yacpki/hash/algo.go | 38 +++++++++++ gyac/yacpki/prv.go | 32 +++------ gyac/yacpki/signed-data.go | 26 ++++--- 18 files changed, 384 insertions(+), 252 deletions(-) create mode 100644 gyac/yacpki/av.go create mode 100644 gyac/yacpki/doc.go create mode 100644 gyac/yacpki/ed25519-blake2b/algo.go create mode 100644 gyac/yacpki/ed25519-blake2b/kp.go create mode 100644 gyac/yacpki/ed25519-blake2b/prv.go create mode 100644 gyac/yacpki/ed25519-blake2b/verify.go delete mode 100644 gyac/yacpki/gost.go create mode 100644 gyac/yacpki/gost/gost.go create mode 100644 gyac/yacpki/gost/kp.go create mode 100644 gyac/yacpki/gost/signer.go create mode 100644 gyac/yacpki/gost/verify.go create mode 100644 gyac/yacpki/hash/algo.go diff --git a/gyac/yacpki/algo.go b/gyac/yacpki/algo.go index 7307ed8..fcee50c 100644 --- a/gyac/yacpki/algo.go +++ b/gyac/yacpki/algo.go @@ -1,54 +1,12 @@ package yacpki import ( - "bytes" - "hash" - - "github.com/google/uuid" - "go.cypherpunks.su/gogost/v6/gost34112012256" - "go.cypherpunks.su/gogost/v6/gost34112012512" - "golang.org/x/crypto/blake2b" - - "go.cypherpunks.su/yac/gyac" - "go.cypherpunks.su/yac/gyac/yacpki/utils" + "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b" + "go.cypherpunks.su/yac/gyac/yacpki/gost" ) const ( - AlgoEd25519BLAKE2b = "ed25519-blake2b" - AlgoGOST3410256A = "gost3410-256A" - AlgoGOST3410512C = "gost3410-512C" - AlgoStreebog256 = "streebog256" - AlgoStreebog512 = "streebog512" + Ed25519BLAKE2b = ed25519blake2b.Ed25519BLAKE2b + GOST3410256A = gost.GOST3410256A + GOST3410512C = gost.GOST3410512C ) - -var HashToNew = map[string]func() hash.Hash{ - AlgoStreebog256: gost34112012256.New, - AlgoStreebog512: gost34112012512.New, -} - -type AV struct { - A string `yac:"a"` - V []byte `yac:"v"` -} - -func (av *AV) Id() (id uuid.UUID) { - var hasher hash.Hash - switch av.A { - case AlgoEd25519BLAKE2b: - var err error - hasher, err = blake2b.New256(nil) - if err != nil { - panic(err) - } - case AlgoGOST3410256A, AlgoGOST3410512C: - hasher = gost34112012256.New() - default: - panic("unsupported algorithm") - } - utils.MustWrite(hasher, gyac.FromGo(av).Encode(nil)) - id, err := uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil))) - if err != nil { - panic(err) - } - return id -} diff --git a/gyac/yacpki/av.go b/gyac/yacpki/av.go new file mode 100644 index 0000000..acfdc90 --- /dev/null +++ b/gyac/yacpki/av.go @@ -0,0 +1,39 @@ +package yacpki + +import ( + "bytes" + "hash" + + "github.com/google/uuid" + + "go.cypherpunks.su/yac/gyac" + pkihash "go.cypherpunks.su/yac/gyac/yacpki/hash" + "go.cypherpunks.su/yac/gyac/yacpki/utils" +) + +// Algorithm-value often used structure. +type AV struct { + A string `yac:"a"` + V []byte `yac:"v"` +} + +// Calculate UUID of the AV. UUIDv4 is generated from the hash output of +// the av structure. Hash algorithm selection depends on av.A. uuid.Nil +// is returned if algorithm is not supported. +func (av *AV) Id() (id uuid.UUID) { + var hasher hash.Hash + switch av.A { + case Ed25519BLAKE2b: + hasher = pkihash.ByName(pkihash.BLAKE2b256) + case GOST3410256A, GOST3410512C: + hasher = pkihash.ByName(pkihash.Streebog256) + default: + return uuid.Nil + } + utils.MustWrite(hasher, gyac.FromGo(av).Encode(nil)) + id, err := uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil))) + if err != nil { + panic(err) + } + return id +} diff --git a/gyac/yacpki/cer.go b/gyac/yacpki/cer.go index 255fb46..62b7a3a 100644 --- a/gyac/yacpki/cer.go +++ b/gyac/yacpki/cer.go @@ -4,31 +4,29 @@ import ( "crypto" "errors" "fmt" - "hash" "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" "go.cypherpunks.su/yac/gyac/mapstruct" - "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" - "go.cypherpunks.su/yac/gyac/yacpki/utils" + ed25519blake2b "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b" + "go.cypherpunks.su/yac/gyac/yacpki/gost" ) const ( - KUCA = "ca" - KUSig = "sig" + KUCA = "ca" // CA-capable key usage + KUSig = "sig" // Signing-capable key usage ) +// Public key. type Pub struct { A string `yac:"a"` V []byte `yac:"v"` Id uuid.UUID `yac:"id"` } +// Certificate load (contents). type CerLoad struct { KU *map[string]*struct{} `yac:"ku,omitempty"` Subj map[string]string `yac:"sub"` @@ -36,15 +34,9 @@ type CerLoad struct { Pub []Pub `yac:"pub"` } -func CerParse(data []byte) (sd *SignedData, tail []byte, err error) { - sd, tail, err = SignedDataParse(data) - if err != nil { - return - } - err = sd.CerParse() - return -} - +// Parse SignedData contents as CerLoad (certificate) and check its +// signatures necessary structure. sd.Load.V will hold the CerLoad in +// case of success. func (sd *SignedData) CerParse() error { if sd.Load.T != "cer" { return errors.New("CerParse: wrong load type") @@ -104,16 +96,28 @@ func (sd *SignedData) CerParse() error { return nil } -func (cer *CerLoad) Can(ku string) bool { - if cer.KU == nil { - return false +// Parse YAC-encoded data as SignedData with the CerLoad (certificate) contents. +func CerParse(data []byte) (sd *SignedData, tail []byte, err error) { + sd, tail, err = SignedDataParse(data) + if err != nil { + return } - if _, ok := (*cer.KU)[ku]; !ok { + err = sd.CerParse() + return +} + +// Check if cer certificate has desired ku capability. +func (cer *CerLoad) Can(ku string) (yes bool) { + if cer.KU == nil { return false } - return true + _, yes = (*cer.KU)[ku] + return } +// Sign the current SignedData, having CerLoad payload with the provided +// parent's CerLoad and prv key. Certificate's CID will be automatically +// generated UUIDv7. since and till times must not have nanoseconds part. func (sd *SignedData) CerIssueWith( parent *CerLoad, prv crypto.Signer, @@ -129,40 +133,23 @@ func (sd *SignedData) CerIssueWith( var ErrSigInvalid = errors.New("signature is invalid") +// Verify signature of signed data. ErrSigInvalid will be returned in +// case of invalid signature. func (cer *CerLoad) CheckSignature(signed, 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 AlgoEd25519BLAKE2b: - if len(pub.V) != ed25519.PublicKeySize { - err = errors.New("invalid ed25519 public key size") - return - } - if !ed25519.Verify(ed25519.PublicKey(pub.V), signed, signature) { - err = errors.New("invalid ed25519 signature") - return - } - case AlgoGOST3410256A, AlgoGOST3410512C: - var pk *gost3410.PublicKey - pk, err = gost3410.NewPublicKeyBE(GOST3410CurveByName(pub.A), pub.V) - if err != nil { - return - } - var hasher hash.Hash - switch pub.A { - case AlgoGOST3410256A: - hasher = gost34112012256.New() - case AlgoGOST3410512C: - hasher = gost34112012512.New() + case Ed25519BLAKE2b: + valid, err = ed25519blake2b.Verify(pub.V, signed, signature) + if !valid { + err = ErrSigInvalid } - utils.MustWrite(hasher, signed) - hsh := hasher.Sum(nil) - var valid bool - valid, err = pk.VerifyDigest(hsh, - append(signature[len(signature)/2:], signature[:len(signature)/2]...)) + case GOST3410256A, GOST3410512C: + valid, err = gost.Verify(pub.A, pub.V, signed, signature) if !valid { err = ErrSigInvalid } @@ -172,6 +159,8 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) { return } +// Verify SignedData CerLoad certificate's signature with provided parent. +// Currently only single signature can be verified. func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) { if len(sd.Sigs) != 1 { err = errors.New("can verify only single signature") @@ -190,6 +179,8 @@ func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) { return parent.CheckSignature(gyac.FromGo(tbs).Encode(nil), sig.Sign.V) } +// Get CerLoad from SignedData. +// Returns nil if SignedData does not hold it (or it is not yet parsed). func (sd *SignedData) CerLoad() *CerLoad { if sd.Load.T != "cer" { return nil @@ -201,6 +192,8 @@ func (sd *SignedData) CerLoad() *CerLoad { return nil } +// Verify sd SignedData CerLoad certificate against cers chain of +// certificate authority ones at specified point of time t. func (sd *SignedData) CerVerify(cers []*SignedData, t time.Time) (err error) { { exp := *(sd.Sigs[0].TBS.Exp) diff --git a/gyac/yacpki/cmd/yacertool/main.go b/gyac/yacpki/cmd/yacertool/main.go index 1fe1aa0..446ccd3 100644 --- a/gyac/yacpki/cmd/yacertool/main.go +++ b/gyac/yacpki/cmd/yacertool/main.go @@ -2,20 +2,17 @@ package main import ( "crypto" - "crypto/rand" "errors" "flag" - "io" "log" "os" "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/ed25519-blake2b/ed25519" + ed25519blake2b "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b" + "go.cypherpunks.su/yac/gyac/yacpki/gost" "go.cypherpunks.su/yac/gyac/yacpki/utils" ) @@ -55,7 +52,7 @@ 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", "gost3410-256A", "Public key algorithm") + algo := flag.String("algo", "", "Public key algorithm") issuingPrv := flag.String("ca-prv", "", "Path to private key file for issuing with") reuseKey := flag.Bool("reuse-key", false, @@ -101,7 +98,7 @@ func main() { if *issuingPrv == "" { log.Fatal("no -ca-key is set") } - caPrv, err = yacpki.PrvParse(utils.MustReadFile(*issuingPrv)) + caPrv, _, err = yacpki.PrvParse(utils.MustReadFile(*issuingPrv)) if err != nil { log.Fatal(err) } @@ -125,79 +122,38 @@ func main() { } var prv crypto.Signer - var pubRaw []byte - switch *algo { - case yacpki.AlgoEd25519BLAKE2b: - if *reuseKey { - prv, err = yacpki.PrvParse(utils.MustReadFile(*prvPath)) - if err != nil { - log.Fatal(err) - } - prvEd25519 := prv.(ed25519.PrivateKey) - pubRaw = prvEd25519[ed25519.SeedSize:] - } else { - var prvEd25519 ed25519.PrivateKey - var pubEd25519 ed25519.PublicKey - pubEd25519, prvEd25519, err = ed25519.GenerateKey(rand.Reader) - if err != nil { - log.Fatal(err) - } - prv = prvEd25519 - pubRaw = pubEd25519[:] - err = os.WriteFile(*prvPath, gyac.FromGo( - yacpki.AV{A: *algo, V: prvEd25519.Seed()}, - ).Encode(nil), 0o600) - if err != nil { - log.Fatal(err) - } + var prvRaw []byte + var pub []byte + if *reuseKey { + prv, pub, err = yacpki.PrvParse(utils.MustReadFile(*prvPath)) + if err != nil { + log.Fatal(err) } - default: // GOST - curve := yacpki.GOST3410CurveByName(*algo) - if curve == nil { - log.Fatal("unknown -algo specified") + } else { + switch *algo { + case yacpki.Ed25519BLAKE2b: + prv, prvRaw, pub, err = ed25519blake2b.NewKeypair() + case yacpki.GOST3410256A, yacpki.GOST3410512C: + prv, prvRaw, pub, err = gost.NewKeypair(*algo) + default: + err = errors.New("unknown -algo specified") } - var signer *yacpki.GOSTSigner - if *reuseKey { - prv, err = yacpki.PrvParse(utils.MustReadFile(*prvPath)) - if err != nil { - log.Fatal(err) - } - signer = prv.(*yacpki.GOSTSigner) - if signer.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) - } - var prvKey *gost3410.PrivateKey - prvKey, err = gost3410.NewPrivateKeyBE(curve, prvRaw) - if err != nil { - log.Fatal(err) - } - raw := gyac.FromGo(yacpki.AV{A: *algo, V: prvKey.RawBE()}).Encode(nil) - prv, err = yacpki.PrvParse(raw) - if err != nil { - log.Fatal(err) - } - err = os.WriteFile(*prvPath, raw, 0o600) - if err != nil { - log.Fatal(err) - } - signer = prv.(*yacpki.GOSTSigner) + if err != nil { + log.Fatal(err) } - var pub *gost3410.PublicKey - pub, err = signer.Prv.PublicKey() + err = os.WriteFile( + *prvPath, + gyac.FromGo(yacpki.AV{A: *algo, V: prvRaw}).Encode(nil), + 0o600, + ) if err != nil { log.Fatal(err) } - pubRaw = pub.RawBE() } - pubMap := yacpki.Pub{A: *algo, V: pubRaw} + pubMap := yacpki.Pub{A: *algo, V: pub} { - av := yacpki.AV{A: *algo, V: pubRaw} + av := yacpki.AV{A: *algo, V: pub} pubMap.Id = av.Id() } cerLoad := yacpki.CerLoad{Subj: subj, Pub: []yacpki.Pub{pubMap}} diff --git a/gyac/yacpki/cmd/yacsdtool/main.go b/gyac/yacpki/cmd/yacsdtool/main.go index 16d08f2..565fced 100644 --- a/gyac/yacpki/cmd/yacsdtool/main.go +++ b/gyac/yacpki/cmd/yacsdtool/main.go @@ -12,6 +12,7 @@ import ( "go.cypherpunks.su/yac/gyac" "go.cypherpunks.su/yac/gyac/yacpki" + pkihash "go.cypherpunks.su/yac/gyac/yacpki/hash" "go.cypherpunks.su/yac/gyac/yacpki/utils" ) @@ -21,7 +22,7 @@ func main() { 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") + verify := flag.Bool("verify", false, "Verify with provided -cer") flag.Parse() log.SetFlags(log.Lshortfile) @@ -39,17 +40,16 @@ func main() { if *prvPath == "" { log.Fatal("no -prv is set") } - signer, err = yacpki.PrvParse(utils.MustReadFile(*prvPath)) + signer, _, err = yacpki.PrvParse(utils.MustReadFile(*prvPath)) if err != nil { log.Fatal(err) } } - hashNew := yacpki.HashToNew[*hashAlgo] - if hashNew == nil { + hasher := pkihash.ByName(*hashAlgo) + if hasher == nil { log.Fatal("unknown -hash specified") } - hasher := hashNew() _, err = io.Copy(hasher, bufio.NewReader(os.Stdin)) if err != nil { log.Fatal(err) diff --git a/gyac/yacpki/doc.go b/gyac/yacpki/doc.go new file mode 100644 index 0000000..64fda62 --- /dev/null +++ b/gyac/yacpki/doc.go @@ -0,0 +1,2 @@ +// yacpki provides PKI-related capabilities based on YAC encoded formats. +package yacpki diff --git a/gyac/yacpki/ed25519-blake2b/algo.go b/gyac/yacpki/ed25519-blake2b/algo.go new file mode 100644 index 0000000..2bacaf6 --- /dev/null +++ b/gyac/yacpki/ed25519-blake2b/algo.go @@ -0,0 +1,3 @@ +package ed25519blake2b + +const Ed25519BLAKE2b = "ed25519-blake2b" diff --git a/gyac/yacpki/ed25519-blake2b/kp.go b/gyac/yacpki/ed25519-blake2b/kp.go new file mode 100644 index 0000000..e5975ff --- /dev/null +++ b/gyac/yacpki/ed25519-blake2b/kp.go @@ -0,0 +1,21 @@ +package ed25519blake2b + +import ( + "crypto" + "crypto/rand" + + "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" +) + +func NewKeypair() (signer crypto.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 + prv = prvEd.Seed() + pub = pubEd[:] + return +} diff --git a/gyac/yacpki/ed25519-blake2b/prv.go b/gyac/yacpki/ed25519-blake2b/prv.go new file mode 100644 index 0000000..4764214 --- /dev/null +++ b/gyac/yacpki/ed25519-blake2b/prv.go @@ -0,0 +1,19 @@ +package ed25519blake2b + +import ( + "crypto" + "errors" + + "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" +) + +func NewSigner(v []byte) (prv crypto.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 + return +} diff --git a/gyac/yacpki/ed25519-blake2b/verify.go b/gyac/yacpki/ed25519-blake2b/verify.go new file mode 100644 index 0000000..c8f5421 --- /dev/null +++ b/gyac/yacpki/ed25519-blake2b/verify.go @@ -0,0 +1,16 @@ +package ed25519blake2b + +import ( + "errors" + + "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" +) + +func Verify(pub, signed, signature []byte) (valid bool, err error) { + if len(pub) != ed25519.PublicKeySize { + err = errors.New("invalid ed25519 public key size") + return + } + valid = ed25519.Verify(ed25519.PublicKey(pub), signed, signature) + return +} diff --git a/gyac/yacpki/gost.go b/gyac/yacpki/gost.go deleted file mode 100644 index f6e00e6..0000000 --- a/gyac/yacpki/gost.go +++ /dev/null @@ -1,47 +0,0 @@ -package yacpki - -import ( - "crypto" - "hash" - "io" - "log" - - "go.cypherpunks.su/gogost/v6/gost3410" -) - -type GOSTSigner struct { - Prv *gost3410.PrivateKey - Hasher func() hash.Hash -} - -func (s *GOSTSigner) Public() crypto.PublicKey { - return s.Prv.Public() -} - -func (s *GOSTSigner) Sign( - rand io.Reader, - msg []byte, - opts crypto.SignerOpts, -) (signature []byte, err error) { - h := s.Hasher() - h.Write(msg) - dgst := h.Sum(nil) - signature, err = s.Prv.Sign(rand, dgst, opts) - if err != nil { - return - } - signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...) - return -} - -func GOST3410CurveByName(name string) (curve *gost3410.Curve) { - switch name { - case AlgoGOST3410256A: - curve = gost3410.CurveIdtc26gost341012256paramSetA() - case AlgoGOST3410512C: - curve = gost3410.CurveIdtc26gost341012512paramSetC() - default: - log.Fatal("unknown curve") - } - return -} diff --git a/gyac/yacpki/gost/gost.go b/gyac/yacpki/gost/gost.go new file mode 100644 index 0000000..bffbd62 --- /dev/null +++ b/gyac/yacpki/gost/gost.go @@ -0,0 +1,20 @@ +package gost + +import ( + "go.cypherpunks.su/gogost/v6/gost3410" +) + +const ( + GOST3410256A = "gost3410-256A" + GOST3410512C = "gost3410-512C" +) + +func CurveByName(name string) (curve *gost3410.Curve) { + switch name { + case GOST3410256A: + curve = gost3410.CurveIdtc26gost341012256paramSetA() + case GOST3410512C: + curve = gost3410.CurveIdtc26gost341012512paramSetC() + } + return +} diff --git a/gyac/yacpki/gost/kp.go b/gyac/yacpki/gost/kp.go new file mode 100644 index 0000000..a9836dc --- /dev/null +++ b/gyac/yacpki/gost/kp.go @@ -0,0 +1,30 @@ +package gost + +import ( + "crypto" + "crypto/rand" + "errors" + "io" + + "go.cypherpunks.su/gogost/v6/gost3410" +) + +func NewKeypair(algo string) (signer crypto.Signer, prv, pub []byte, err error) { + curve := CurveByName(algo) + if curve == nil { + err = errors.New("unknown curve") + return + } + prv = make([]byte, curve.PointSize()) + _, err = io.ReadFull(rand.Reader, prv) + if err != nil { + return + } + var pk *gost3410.PrivateKey + pk, err = gost3410.NewPrivateKeyBE(curve, prv) + if err != nil { + return + } + signer, pub, err = NewSigner(algo, pk.RawBE()) + return +} diff --git a/gyac/yacpki/gost/signer.go b/gyac/yacpki/gost/signer.go new file mode 100644 index 0000000..f1f0772 --- /dev/null +++ b/gyac/yacpki/gost/signer.go @@ -0,0 +1,64 @@ +package gost + +import ( + "crypto" + "hash" + "io" + + "go.cypherpunks.su/gogost/v6/gost3410" + "go.cypherpunks.su/gogost/v6/gost34112012256" + "go.cypherpunks.su/gogost/v6/gost34112012512" +) + +type Signer struct { + Prv *gost3410.PrivateKey + Hasher func() hash.Hash +} + +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) { + h := s.Hasher() + h.Write(msg) + dgst := h.Sum(nil) + signature, err = s.Prv.Sign(rand, dgst, opts) + if err != nil { + return + } + signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...) + return +} + +func NewSigner(a string, v []byte) (prv crypto.Signer, pub []byte, err error) { + signer := Signer{} + switch a { + case GOST3410256A: + signer.Hasher = gost34112012256.New + signer.Prv, err = gost3410.NewPrivateKeyBE( + gost3410.CurveIdtc26gost341012256paramSetA(), v, + ) + case GOST3410512C: + signer.Hasher = gost34112012512.New + signer.Prv, err = gost3410.NewPrivateKeyBE( + gost3410.CurveIdtc26gost341012512paramSetC(), v, + ) + default: + panic("unknown GOST algorithm") + } + if err != nil { + return + } + prv = &signer + var pk *gost3410.PublicKey + pk, err = signer.Prv.PublicKey() + if err == nil { + pub = pk.RawBE() + } + return +} diff --git a/gyac/yacpki/gost/verify.go b/gyac/yacpki/gost/verify.go new file mode 100644 index 0000000..6412ed3 --- /dev/null +++ b/gyac/yacpki/gost/verify.go @@ -0,0 +1,30 @@ +package gost + +import ( + "hash" + + "go.cypherpunks.su/gogost/v6/gost3410" + "go.cypherpunks.su/gogost/v6/gost34112012256" + "go.cypherpunks.su/gogost/v6/gost34112012512" + "go.cypherpunks.su/yac/gyac/yacpki/utils" +) + +func Verify(algo string, pub, signed, signature []byte) (valid bool, err error) { + var pk *gost3410.PublicKey + pk, err = gost3410.NewPublicKeyBE(CurveByName(algo), pub) + if err != nil { + return + } + var hasher hash.Hash + switch algo { + case GOST3410256A: + hasher = gost34112012256.New() + case GOST3410512C: + hasher = gost34112012512.New() + } + utils.MustWrite(hasher, signed) + hsh := hasher.Sum(nil) + valid, err = pk.VerifyDigest(hsh, + append(signature[len(signature)/2:], signature[:len(signature)/2]...)) + return +} diff --git a/gyac/yacpki/hash/algo.go b/gyac/yacpki/hash/algo.go new file mode 100644 index 0000000..d5a0040 --- /dev/null +++ b/gyac/yacpki/hash/algo.go @@ -0,0 +1,38 @@ +package hash + +import ( + "hash" + + "go.cypherpunks.su/gogost/v6/gost34112012256" + "go.cypherpunks.su/gogost/v6/gost34112012512" + "golang.org/x/crypto/blake2b" +) + +const ( + Streebog256 = "streebog256" + Streebog512 = "streebog512" + BLAKE2b = "blake2b" + BLAKE2b256 = "blake2b256" +) + +func ByName(name string) hash.Hash { + switch name { + case Streebog256: + return gost34112012256.New() + case Streebog512: + return gost34112012512.New() + case BLAKE2b: + h, err := blake2b.New512(nil) + if err != nil { + panic(err) + } + return h + case BLAKE2b256: + h, err := blake2b.New256(nil) + if err != nil { + panic(err) + } + return h + } + return nil +} diff --git a/gyac/yacpki/prv.go b/gyac/yacpki/prv.go index 484de41..feb0a83 100644 --- a/gyac/yacpki/prv.go +++ b/gyac/yacpki/prv.go @@ -5,15 +5,13 @@ import ( "errors" "fmt" - "go.cypherpunks.su/gogost/v6/gost3410" - "go.cypherpunks.su/gogost/v6/gost34112012256" - "go.cypherpunks.su/gogost/v6/gost34112012512" - "go.cypherpunks.su/yac/gyac/mapstruct" - "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" + ed25519blake2b "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b" + "go.cypherpunks.su/yac/gyac/yacpki/gost" ) -func PrvParse(data []byte) (prv crypto.Signer, err error) { +// Parse private key contained in AV YAC-encoded structure. +func PrvParse(data []byte) (prv crypto.Signer, pub []byte, err error) { var av AV var tail []byte tail, err = mapstruct.Decode(&av, data) @@ -25,24 +23,10 @@ func PrvParse(data []byte) (prv crypto.Signer, err error) { return } switch av.A { - case AlgoEd25519BLAKE2b: - prv = ed25519.NewKeyFromSeed(av.V) - if len(av.V) != ed25519.SeedSize { - err = errors.New("wrong ed25519 private key size") - return - } - case AlgoGOST3410256A: - signer := &GOSTSigner{Hasher: gost34112012256.New} - signer.Prv, err = gost3410.NewPrivateKeyBE( - gost3410.CurveIdtc26gost341012256paramSetA(), av.V, - ) - prv = signer - case AlgoGOST3410512C: - signer := &GOSTSigner{Hasher: gost34112012512.New} - signer.Prv, err = gost3410.NewPrivateKeyBE( - gost3410.CurveIdtc26gost341012512paramSetC(), av.V, - ) - prv = signer + case Ed25519BLAKE2b: + prv, pub, err = ed25519blake2b.NewSigner(av.V) + case GOST3410256A, GOST3410512C: + prv, pub, err = gost.NewSigner(av.A, av.V) default: err = fmt.Errorf("unknown private key algo: %s", av.A) } diff --git a/gyac/yacpki/signed-data.go b/gyac/yacpki/signed-data.go index c753372..86f35b0 100644 --- a/gyac/yacpki/signed-data.go +++ b/gyac/yacpki/signed-data.go @@ -45,16 +45,7 @@ type SignedData struct { Sigs []*Sig `yac:"sigs"` } -func SignedDataParse(data []byte) (sd *SignedData, tail []byte, err error) { - var item gyac.Item - item, tail, err = gyac.Decode(data) - if err != nil { - return - } - sd, err = SignedDataParseItem(item) - return -} - +// Parse signed-data from decoded item. func SignedDataParseItem(item gyac.Item) (sd *SignedData, err error) { if item.T != types.Map { err = errors.New("SignedDataParse: non-map") @@ -125,6 +116,21 @@ func SignedDataParseItem(item gyac.Item) (sd *SignedData, err error) { return } +// Parse signed-data from YAC-encoded data. This is just a wrapper over +// SignedDataParseItem. +func SignedDataParse(data []byte) (sd *SignedData, tail []byte, err error) { + var item gyac.Item + item, tail, err = gyac.Decode(data) + if err != nil { + return + } + sd, err = SignedDataParseItem(item) + return +} + +// Sign SignedData's contents and sigTBS corresponding data with the +// provided prv signer, having parent certificate. Signature is appended +// to the sd.Sigs. parent certificate must have "sig" key-usage. func (sd *SignedData) SignWith( parent *CerLoad, prv crypto.Signer, -- 2.48.1