From 8e357496d8335eb86f80e7346fac39f9cc633bb8 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 12:11:05 +0300 Subject: [PATCH 1/6] [#3] api-go: Go version up Update go version 1.17 -> 1.18 Signed-off-by: Dmitrii Stepanov --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4eddb9e..e17e4e2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/TrueCloudLab/frostfs-api-go/v2 -go 1.17 +go 1.18 require ( github.com/TrueCloudLab/frostfs-crypto v0.5.0 -- 2.45.2 From f10ecb7fb460f994933acb5b94db84779d1af555 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 13:01:18 +0300 Subject: [PATCH 2/6] [#3] signature: Refactor sign and verify Split methods to separate files, drop redundant intefaces Signed-off-by: Dmitrii Stepanov --- go.mod | 3 +- go.sum | 8 +- signature/body.go | 115 +++++++++++++ signature/marshaller.go | 26 +++ signature/sign.go | 357 ++++----------------------------------- signature/verify.go | 111 ++++++++++++ util/collection/slice.go | 17 ++ 7 files changed, 312 insertions(+), 325 deletions(-) create mode 100644 signature/body.go create mode 100644 signature/marshaller.go create mode 100644 signature/verify.go create mode 100644 util/collection/slice.go diff --git a/go.mod b/go.mod index e17e4e2..38edb52 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/TrueCloudLab/frostfs-crypto v0.5.0 github.com/stretchr/testify v1.7.0 + golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.0 ) @@ -16,7 +17,7 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect - golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect diff --git a/go.sum b/go.sum index 68f8351..06f1643 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -64,6 +64,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -87,8 +89,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -98,7 +101,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/signature/body.go b/signature/body.go new file mode 100644 index 0000000..0d5c0cd --- /dev/null +++ b/signature/body.go @@ -0,0 +1,115 @@ +package signature + +import ( + "fmt" + + "github.com/TrueCloudLab/frostfs-api-go/v2/accounting" + "github.com/TrueCloudLab/frostfs-api-go/v2/container" + "github.com/TrueCloudLab/frostfs-api-go/v2/netmap" + "github.com/TrueCloudLab/frostfs-api-go/v2/object" + "github.com/TrueCloudLab/frostfs-api-go/v2/reputation" + "github.com/TrueCloudLab/frostfs-api-go/v2/session" +) + +func serviceMessageBody(req interface{}) stableMarshaler { + switch v := req.(type) { + default: + panic(fmt.Sprintf("unsupported session message %T", req)) + + /* Accounting */ + case *accounting.BalanceRequest: + return v.GetBody() + case *accounting.BalanceResponse: + return v.GetBody() + + /* Session */ + case *session.CreateRequest: + return v.GetBody() + case *session.CreateResponse: + return v.GetBody() + + /* Container */ + case *container.PutRequest: + return v.GetBody() + case *container.PutResponse: + return v.GetBody() + case *container.DeleteRequest: + return v.GetBody() + case *container.DeleteResponse: + return v.GetBody() + case *container.GetRequest: + return v.GetBody() + case *container.GetResponse: + return v.GetBody() + case *container.ListRequest: + return v.GetBody() + case *container.ListResponse: + return v.GetBody() + case *container.SetExtendedACLRequest: + return v.GetBody() + case *container.SetExtendedACLResponse: + return v.GetBody() + case *container.GetExtendedACLRequest: + return v.GetBody() + case *container.GetExtendedACLResponse: + return v.GetBody() + case *container.AnnounceUsedSpaceRequest: + return v.GetBody() + case *container.AnnounceUsedSpaceResponse: + return v.GetBody() + + /* Object */ + case *object.PutRequest: + return v.GetBody() + case *object.PutResponse: + return v.GetBody() + case *object.GetRequest: + return v.GetBody() + case *object.GetResponse: + return v.GetBody() + case *object.HeadRequest: + return v.GetBody() + case *object.HeadResponse: + return v.GetBody() + case *object.SearchRequest: + return v.GetBody() + case *object.SearchResponse: + return v.GetBody() + case *object.DeleteRequest: + return v.GetBody() + case *object.DeleteResponse: + return v.GetBody() + case *object.GetRangeRequest: + return v.GetBody() + case *object.GetRangeResponse: + return v.GetBody() + case *object.GetRangeHashRequest: + return v.GetBody() + case *object.GetRangeHashResponse: + return v.GetBody() + + /* Netmap */ + case *netmap.LocalNodeInfoRequest: + return v.GetBody() + case *netmap.LocalNodeInfoResponse: + return v.GetBody() + case *netmap.NetworkInfoRequest: + return v.GetBody() + case *netmap.NetworkInfoResponse: + return v.GetBody() + case *netmap.SnapshotRequest: + return v.GetBody() + case *netmap.SnapshotResponse: + return v.GetBody() + + /* Reputation */ + case *reputation.AnnounceLocalTrustRequest: + return v.GetBody() + case *reputation.AnnounceLocalTrustResponse: + return v.GetBody() + case *reputation.AnnounceIntermediateResultRequest: + return v.GetBody() + case *reputation.AnnounceIntermediateResultResponse: + return v.GetBody() + } +} diff --git a/signature/marshaller.go b/signature/marshaller.go new file mode 100644 index 0000000..ff9beb3 --- /dev/null +++ b/signature/marshaller.go @@ -0,0 +1,26 @@ +package signature + +type stableMarshaler interface { + StableMarshal([]byte) []byte + StableSize() int +} + +type StableMarshalerWrapper struct { + SM stableMarshaler +} + +func (s StableMarshalerWrapper) ReadSignedData(buf []byte) ([]byte, error) { + if s.SM != nil { + return s.SM.StableMarshal(buf), nil + } + + return nil, nil +} + +func (s StableMarshalerWrapper) SignedDataSize() int { + if s.SM != nil { + return s.SM.StableSize() + } + + return 0 +} diff --git a/signature/sign.go b/signature/sign.go index f1d4cf6..4eca7f5 100644 --- a/signature/sign.go +++ b/signature/sign.go @@ -2,15 +2,9 @@ package signature import ( "crypto/ecdsa" - "errors" "fmt" - "github.com/TrueCloudLab/frostfs-api-go/v2/accounting" - "github.com/TrueCloudLab/frostfs-api-go/v2/container" - "github.com/TrueCloudLab/frostfs-api-go/v2/netmap" - "github.com/TrueCloudLab/frostfs-api-go/v2/object" "github.com/TrueCloudLab/frostfs-api-go/v2/refs" - "github.com/TrueCloudLab/frostfs-api-go/v2/reputation" "github.com/TrueCloudLab/frostfs-api-go/v2/session" "github.com/TrueCloudLab/frostfs-api-go/v2/util/signature" ) @@ -27,169 +21,69 @@ type serviceResponse interface { SetVerificationHeader(*session.ResponseVerificationHeader) } -type stableMarshaler interface { - StableMarshal([]byte) []byte - StableSize() int -} - -type StableMarshalerWrapper struct { - SM stableMarshaler -} - -type metaHeader interface { - stableMarshaler - getOrigin() metaHeader -} - -type verificationHeader interface { - stableMarshaler - - GetBodySignature() *refs.Signature +type signatureReceiver interface { SetBodySignature(*refs.Signature) - GetMetaSignature() *refs.Signature SetMetaSignature(*refs.Signature) - GetOriginSignature() *refs.Signature SetOriginSignature(*refs.Signature) - - setOrigin(stableMarshaler) - getOrigin() verificationHeader -} - -type requestMetaHeader struct { - *session.RequestMetaHeader -} - -type responseMetaHeader struct { - *session.ResponseMetaHeader -} - -type requestVerificationHeader struct { - *session.RequestVerificationHeader -} - -type responseVerificationHeader struct { - *session.ResponseVerificationHeader -} - -func (h *requestMetaHeader) getOrigin() metaHeader { - return &requestMetaHeader{ - RequestMetaHeader: h.GetOrigin(), - } -} - -func (h *responseMetaHeader) getOrigin() metaHeader { - return &responseMetaHeader{ - ResponseMetaHeader: h.GetOrigin(), - } -} - -func (h *requestVerificationHeader) getOrigin() verificationHeader { - if origin := h.GetOrigin(); origin != nil { - return &requestVerificationHeader{ - RequestVerificationHeader: origin, - } - } - - return nil -} - -func (h *requestVerificationHeader) setOrigin(m stableMarshaler) { - if m != nil { - h.SetOrigin(m.(*session.RequestVerificationHeader)) - } -} - -func (r *responseVerificationHeader) getOrigin() verificationHeader { - if origin := r.GetOrigin(); origin != nil { - return &responseVerificationHeader{ - ResponseVerificationHeader: origin, - } - } - - return nil -} - -func (r *responseVerificationHeader) setOrigin(m stableMarshaler) { - if m != nil { - r.SetOrigin(m.(*session.ResponseVerificationHeader)) - } -} - -func (s StableMarshalerWrapper) ReadSignedData(buf []byte) ([]byte, error) { - if s.SM != nil { - return s.SM.StableMarshal(buf), nil - } - - return nil, nil -} - -func (s StableMarshalerWrapper) SignedDataSize() int { - if s.SM != nil { - return s.SM.StableSize() - } - - return 0 } +// SignServiceMessage signes service message with key. func SignServiceMessage(key *ecdsa.PrivateKey, msg interface{}) error { - var ( - body, meta, verifyOrigin stableMarshaler - verifyHdr verificationHeader - verifyHdrSetter func(verificationHeader) - ) - switch v := msg.(type) { case nil: return nil case serviceRequest: - body = serviceMessageBody(v) - meta = v.GetMetaHeader() - verifyHdr = &requestVerificationHeader{new(session.RequestVerificationHeader)} - verifyHdrSetter = func(h verificationHeader) { - v.SetVerificationHeader(h.(*requestVerificationHeader).RequestVerificationHeader) - } - - if h := v.GetVerificationHeader(); h != nil { - verifyOrigin = h - } + return signServiceRequest(key, v) case serviceResponse: - body = serviceMessageBody(v) - meta = v.GetMetaHeader() - verifyHdr = &responseVerificationHeader{new(session.ResponseVerificationHeader)} - verifyHdrSetter = func(h verificationHeader) { - v.SetVerificationHeader(h.(*responseVerificationHeader).ResponseVerificationHeader) - } - - if h := v.GetVerificationHeader(); h != nil { - verifyOrigin = h - } + return signServiceResponse(key, v) default: panic(fmt.Sprintf("unsupported session message %T", v)) } +} - if verifyOrigin == nil { +func signServiceRequest(key *ecdsa.PrivateKey, v serviceRequest) error { + result := &session.RequestVerificationHeader{} + body := serviceMessageBody(v) + meta := v.GetMetaHeader() + header := v.GetVerificationHeader() + if err := signMessageParts(key, body, meta, header, header != nil, result); err != nil { + return err + } + result.SetOrigin(header) + v.SetVerificationHeader(result) + return nil +} + +func signServiceResponse(key *ecdsa.PrivateKey, v serviceResponse) error { + result := &session.ResponseVerificationHeader{} + body := serviceMessageBody(v) + meta := v.GetMetaHeader() + header := v.GetVerificationHeader() + if err := signMessageParts(key, body, meta, header, header != nil, result); err != nil { + return err + } + result.SetOrigin(header) + v.SetVerificationHeader(result) + return nil +} + +func signMessageParts(key *ecdsa.PrivateKey, body, meta, header stableMarshaler, hasHeader bool, result signatureReceiver) error { + if !hasHeader { // sign session message body - if err := signServiceMessagePart(key, body, verifyHdr.SetBodySignature); err != nil { + if err := signServiceMessagePart(key, body, result.SetBodySignature); err != nil { return fmt.Errorf("could not sign body: %w", err) } } // sign meta header - if err := signServiceMessagePart(key, meta, verifyHdr.SetMetaSignature); err != nil { + if err := signServiceMessagePart(key, meta, result.SetMetaSignature); err != nil { return fmt.Errorf("could not sign meta header: %w", err) } // sign verification header origin - if err := signServiceMessagePart(key, verifyOrigin, verifyHdr.SetOriginSignature); err != nil { + if err := signServiceMessagePart(key, header, result.SetOriginSignature); err != nil { return fmt.Errorf("could not sign origin of verification header: %w", err) } - - // wrap origin verification header - verifyHdr.setOrigin(verifyOrigin) - - // update matryoshka verification header - verifyHdrSetter(verifyHdr) - return nil } @@ -212,182 +106,3 @@ func signServiceMessagePart(key *ecdsa.PrivateKey, part stableMarshaler, sigWrit return nil } - -func VerifyServiceMessage(msg interface{}) error { - var ( - meta metaHeader - verify verificationHeader - ) - - switch v := msg.(type) { - case nil: - return nil - case serviceRequest: - meta = &requestMetaHeader{ - RequestMetaHeader: v.GetMetaHeader(), - } - - verify = &requestVerificationHeader{ - RequestVerificationHeader: v.GetVerificationHeader(), - } - case serviceResponse: - meta = &responseMetaHeader{ - ResponseMetaHeader: v.GetMetaHeader(), - } - - verify = &responseVerificationHeader{ - ResponseVerificationHeader: v.GetVerificationHeader(), - } - default: - panic(fmt.Sprintf("unsupported session message %T", v)) - } - - body := serviceMessageBody(msg) - size := body.StableSize() - if sz := meta.StableSize(); sz > size { - size = sz - } - if sz := verify.StableSize(); sz > size { - size = sz - } - - buf := make([]byte, 0, size) - return verifyMatryoshkaLevel(body, meta, verify, buf) -} - -func verifyMatryoshkaLevel(body stableMarshaler, meta metaHeader, verify verificationHeader, buf []byte) error { - if err := verifyServiceMessagePart(meta, verify.GetMetaSignature, buf); err != nil { - return fmt.Errorf("could not verify meta header: %w", err) - } - - origin := verify.getOrigin() - - if err := verifyServiceMessagePart(origin, verify.GetOriginSignature, buf); err != nil { - return fmt.Errorf("could not verify origin of verification header: %w", err) - } - - if origin == nil { - if err := verifyServiceMessagePart(body, verify.GetBodySignature, buf); err != nil { - return fmt.Errorf("could not verify body: %w", err) - } - - return nil - } - - if verify.GetBodySignature() != nil { - return errors.New("body signature at the matryoshka upper level") - } - - return verifyMatryoshkaLevel(body, meta.getOrigin(), origin, buf) -} - -func verifyServiceMessagePart(part stableMarshaler, sigRdr func() *refs.Signature, buf []byte) error { - return signature.VerifyDataWithSource( - &StableMarshalerWrapper{part}, - sigRdr, - signature.WithBuffer(buf), - ) -} - -func serviceMessageBody(req interface{}) stableMarshaler { - switch v := req.(type) { - default: - panic(fmt.Sprintf("unsupported session message %T", req)) - - /* Accounting */ - case *accounting.BalanceRequest: - return v.GetBody() - case *accounting.BalanceResponse: - return v.GetBody() - - /* Session */ - case *session.CreateRequest: - return v.GetBody() - case *session.CreateResponse: - return v.GetBody() - - /* Container */ - case *container.PutRequest: - return v.GetBody() - case *container.PutResponse: - return v.GetBody() - case *container.DeleteRequest: - return v.GetBody() - case *container.DeleteResponse: - return v.GetBody() - case *container.GetRequest: - return v.GetBody() - case *container.GetResponse: - return v.GetBody() - case *container.ListRequest: - return v.GetBody() - case *container.ListResponse: - return v.GetBody() - case *container.SetExtendedACLRequest: - return v.GetBody() - case *container.SetExtendedACLResponse: - return v.GetBody() - case *container.GetExtendedACLRequest: - return v.GetBody() - case *container.GetExtendedACLResponse: - return v.GetBody() - case *container.AnnounceUsedSpaceRequest: - return v.GetBody() - case *container.AnnounceUsedSpaceResponse: - return v.GetBody() - - /* Object */ - case *object.PutRequest: - return v.GetBody() - case *object.PutResponse: - return v.GetBody() - case *object.GetRequest: - return v.GetBody() - case *object.GetResponse: - return v.GetBody() - case *object.HeadRequest: - return v.GetBody() - case *object.HeadResponse: - return v.GetBody() - case *object.SearchRequest: - return v.GetBody() - case *object.SearchResponse: - return v.GetBody() - case *object.DeleteRequest: - return v.GetBody() - case *object.DeleteResponse: - return v.GetBody() - case *object.GetRangeRequest: - return v.GetBody() - case *object.GetRangeResponse: - return v.GetBody() - case *object.GetRangeHashRequest: - return v.GetBody() - case *object.GetRangeHashResponse: - return v.GetBody() - - /* Netmap */ - case *netmap.LocalNodeInfoRequest: - return v.GetBody() - case *netmap.LocalNodeInfoResponse: - return v.GetBody() - case *netmap.NetworkInfoRequest: - return v.GetBody() - case *netmap.NetworkInfoResponse: - return v.GetBody() - case *netmap.SnapshotRequest: - return v.GetBody() - case *netmap.SnapshotResponse: - return v.GetBody() - - /* Reputation */ - case *reputation.AnnounceLocalTrustRequest: - return v.GetBody() - case *reputation.AnnounceLocalTrustResponse: - return v.GetBody() - case *reputation.AnnounceIntermediateResultRequest: - return v.GetBody() - case *reputation.AnnounceIntermediateResultResponse: - return v.GetBody() - } -} diff --git a/signature/verify.go b/signature/verify.go new file mode 100644 index 0000000..092de22 --- /dev/null +++ b/signature/verify.go @@ -0,0 +1,111 @@ +package signature + +import ( + "errors" + "fmt" + + "github.com/TrueCloudLab/frostfs-api-go/v2/refs" + "github.com/TrueCloudLab/frostfs-api-go/v2/session" + "github.com/TrueCloudLab/frostfs-api-go/v2/util/collection" + "github.com/TrueCloudLab/frostfs-api-go/v2/util/signature" +) + +type signatureProvider interface { + GetBodySignature() *refs.Signature + GetMetaSignature() *refs.Signature + GetOriginSignature() *refs.Signature +} + +// VerifyServiceMessage verifies service message. +func VerifyServiceMessage(msg interface{}) error { + switch v := msg.(type) { + case nil: + return nil + case serviceRequest: + return verifyServiceRequest(v) + case serviceResponse: + return verifyServiceResponse(v) + default: + panic(fmt.Sprintf("unsupported session message %T", v)) + } +} + +func verifyServiceRequest(v serviceRequest) error { + meta := v.GetMetaHeader() + verificationHeader := v.GetVerificationHeader() + body := serviceMessageBody(v) + size := collection.Max(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) + buf := make([]byte, 0, size) + return verifyServiceRequestRecursive(body, meta, verificationHeader, buf) +} + +func verifyServiceRequestRecursive(body stableMarshaler, meta *session.RequestMetaHeader, verify *session.RequestVerificationHeader, buf []byte) error { + verificationHeaderOrigin := verify.GetOrigin() + metaOrigin := meta.GetOrigin() + + stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buf) + if err != nil { + return err + } + if stop { + return nil + } + + return verifyServiceRequestRecursive(body, metaOrigin, verificationHeaderOrigin, buf) +} + +func verifyMessageParts(body, meta, originHeader stableMarshaler, hasOriginHeader bool, sigProvider signatureProvider, buf []byte) (stop bool, err error) { + if err := verifyServiceMessagePart(meta, sigProvider.GetMetaSignature, buf); err != nil { + return false, fmt.Errorf("could not verify meta header: %w", err) + } + + if err := verifyServiceMessagePart(originHeader, sigProvider.GetOriginSignature, buf); err != nil { + return false, fmt.Errorf("could not verify origin of verification header: %w", err) + } + + if !hasOriginHeader { + if err := verifyServiceMessagePart(body, sigProvider.GetBodySignature, buf); err != nil { + return false, fmt.Errorf("could not verify body: %w", err) + } + + return true, nil + } + + if sigProvider.GetBodySignature() != nil { + return false, errors.New("body signature misses at the matryoshka upper level") + } + + return false, nil +} + +func verifyServiceResponse(v serviceResponse) error { + meta := v.GetMetaHeader() + verificationHeader := v.GetVerificationHeader() + body := serviceMessageBody(v) + size := collection.Max(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) + buf := make([]byte, 0, size) + return verifyServiceResponseRecursive(body, meta, verificationHeader, buf) +} + +func verifyServiceResponseRecursive(body stableMarshaler, meta *session.ResponseMetaHeader, verify *session.ResponseVerificationHeader, buf []byte) error { + verificationHeaderOrigin := verify.GetOrigin() + metaOrigin := meta.GetOrigin() + + stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buf) + if err != nil { + return err + } + if stop { + return nil + } + + return verifyServiceResponseRecursive(body, metaOrigin, verificationHeaderOrigin, buf) +} + +func verifyServiceMessagePart(part stableMarshaler, sigRdr func() *refs.Signature, buf []byte) error { + return signature.VerifyDataWithSource( + &StableMarshalerWrapper{part}, + sigRdr, + signature.WithBuffer(buf), + ) +} diff --git a/util/collection/slice.go b/util/collection/slice.go new file mode 100644 index 0000000..436f28c --- /dev/null +++ b/util/collection/slice.go @@ -0,0 +1,17 @@ +package collection + +import "golang.org/x/exp/constraints" + +// Max finds maximum value of items. +func Max[T constraints.Ordered](items ...T) T { + if len(items) == 0 { + panic("failed to get max value: empty slice") + } + result := items[0] + for i := 1; i < len(items); i++ { + if items[i] > result { + result = items[i] + } + } + return result +} -- 2.45.2 From 57045b8df41df89cced7b5997279d488704d1fcc Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 14:43:13 +0300 Subject: [PATCH 3/6] [#3] signature: Add benchmarks Add benchmarks for sign and verify methods Signed-off-by: Dmitrii Stepanov --- signature/sign_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/signature/sign_test.go b/signature/sign_test.go index fb508a3..d1d90c8 100644 --- a/signature/sign_test.go +++ b/signature/sign_test.go @@ -70,3 +70,56 @@ func TestBalanceResponse(t *testing.T) { // verification must fail require.Error(t, VerifyServiceMessage(req)) } + +func BenchmarkSignRequest(b *testing.B) { + key, _ := crypto.LoadPrivateKey("Kwk6k2eC3L3QuPvD8aiaNyoSXgQ2YL1bwS5CP1oKoA9waeAze97s") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.StopTimer() + dec := new(accounting.Decimal) + dec.SetValue(100) + + body := new(accounting.BalanceResponseBody) + body.SetBalance(dec) + + meta := new(session.ResponseMetaHeader) + meta.SetTTL(1) + + resp := new(accounting.BalanceResponse) + resp.SetBody(body) + resp.SetMetaHeader(meta) + + b.StartTimer() + SignServiceMessage(key, resp) + } +} + +func BenchmarkVerifyRequest(b *testing.B) { + key, _ := crypto.LoadPrivateKey("Kwk6k2eC3L3QuPvD8aiaNyoSXgQ2YL1bwS5CP1oKoA9waeAze97s") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.StopTimer() + dec := new(accounting.Decimal) + dec.SetValue(100) + + body := new(accounting.BalanceResponseBody) + body.SetBalance(dec) + + meta := new(session.ResponseMetaHeader) + meta.SetTTL(1) + + resp := new(accounting.BalanceResponse) + resp.SetBody(body) + resp.SetMetaHeader(meta) + SignServiceMessage(key, resp) + b.StartTimer() + + VerifyServiceMessage(resp) + } +} -- 2.45.2 From 635fac42edbba30b71dfb050aa20392bfb1c7a81 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 17:47:12 +0300 Subject: [PATCH 4/6] [#3] signature: Sign parts in parallel Sign request/response parts in parallel Signed-off-by: Dmitrii Stepanov --- go.mod | 1 + go.sum | 2 ++ signature/sign.go | 31 +++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 38edb52..3f35113 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect diff --git a/go.sum b/go.sum index 06f1643..4d25c97 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/signature/sign.go b/signature/sign.go index 4eca7f5..ada0aef 100644 --- a/signature/sign.go +++ b/signature/sign.go @@ -7,6 +7,7 @@ import ( "github.com/TrueCloudLab/frostfs-api-go/v2/refs" "github.com/TrueCloudLab/frostfs-api-go/v2/session" "github.com/TrueCloudLab/frostfs-api-go/v2/util/signature" + "golang.org/x/sync/errgroup" ) type serviceRequest interface { @@ -68,23 +69,33 @@ func signServiceResponse(key *ecdsa.PrivateKey, v serviceResponse) error { } func signMessageParts(key *ecdsa.PrivateKey, body, meta, header stableMarshaler, hasHeader bool, result signatureReceiver) error { + eg := &errgroup.Group{} if !hasHeader { // sign session message body - if err := signServiceMessagePart(key, body, result.SetBodySignature); err != nil { - return fmt.Errorf("could not sign body: %w", err) - } + eg.Go(func() error { + if err := signServiceMessagePart(key, body, result.SetBodySignature); err != nil { + return fmt.Errorf("could not sign body: %w", err) + } + return nil + }) } // sign meta header - if err := signServiceMessagePart(key, meta, result.SetMetaSignature); err != nil { - return fmt.Errorf("could not sign meta header: %w", err) - } + eg.Go(func() error { + if err := signServiceMessagePart(key, meta, result.SetMetaSignature); err != nil { + return fmt.Errorf("could not sign meta header: %w", err) + } + return nil + }) // sign verification header origin - if err := signServiceMessagePart(key, header, result.SetOriginSignature); err != nil { - return fmt.Errorf("could not sign origin of verification header: %w", err) - } - return nil + eg.Go(func() error { + if err := signServiceMessagePart(key, header, result.SetOriginSignature); err != nil { + return fmt.Errorf("could not sign origin of verification header: %w", err) + } + return nil + }) + return eg.Wait() } func signServiceMessagePart(key *ecdsa.PrivateKey, part stableMarshaler, sigWrite func(*refs.Signature)) error { -- 2.45.2 From d11531446b71071c3b591fc955ed8540ca19f668 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 18:52:50 +0300 Subject: [PATCH 5/6] [#3] signature: Verify parts in parallel Verify request/response parts in parallel Signed-off-by: Dmitrii Stepanov --- go.mod | 4 +-- go.sum | 3 +- signature/verify.go | 73 ++++++++++++++++++++++++++++------------ util/collection/slice.go | 17 ---------- 4 files changed, 54 insertions(+), 43 deletions(-) delete mode 100644 util/collection/slice.go diff --git a/go.mod b/go.mod index 3f35113..0953130 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/TrueCloudLab/frostfs-crypto v0.5.0 github.com/stretchr/testify v1.7.0 - golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 + golang.org/x/sync v0.1.0 google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.0 ) @@ -14,10 +14,10 @@ require ( github.com/TrueCloudLab/rfc6979 v0.3.0 // indirect github.com/davecgh/go-spew v1.1.0 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect - golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect diff --git a/go.sum b/go.sum index 4d25c97..7a8ad3d 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -64,8 +65,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/signature/verify.go b/signature/verify.go index 092de22..c8ebee1 100644 --- a/signature/verify.go +++ b/signature/verify.go @@ -6,8 +6,8 @@ import ( "github.com/TrueCloudLab/frostfs-api-go/v2/refs" "github.com/TrueCloudLab/frostfs-api-go/v2/session" - "github.com/TrueCloudLab/frostfs-api-go/v2/util/collection" "github.com/TrueCloudLab/frostfs-api-go/v2/util/signature" + "golang.org/x/sync/errgroup" ) type signatureProvider interface { @@ -16,6 +16,12 @@ type signatureProvider interface { GetOriginSignature() *refs.Signature } +type buffers struct { + Body []byte + Meta []byte + Header []byte +} + // VerifyServiceMessage verifies service message. func VerifyServiceMessage(msg interface{}) error { switch v := msg.(type) { @@ -34,16 +40,23 @@ func verifyServiceRequest(v serviceRequest) error { meta := v.GetMetaHeader() verificationHeader := v.GetVerificationHeader() body := serviceMessageBody(v) - size := collection.Max(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) - buf := make([]byte, 0, size) - return verifyServiceRequestRecursive(body, meta, verificationHeader, buf) + buffers := createBuffers(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) + return verifyServiceRequestRecursive(body, meta, verificationHeader, buffers) } -func verifyServiceRequestRecursive(body stableMarshaler, meta *session.RequestMetaHeader, verify *session.RequestVerificationHeader, buf []byte) error { +func createBuffers(bodySize, metaSize, headerSize int) *buffers { + return &buffers{ + Body: make([]byte, 0, bodySize), + Meta: make([]byte, 0, metaSize), + Header: make([]byte, 0, headerSize), + } +} + +func verifyServiceRequestRecursive(body stableMarshaler, meta *session.RequestMetaHeader, verify *session.RequestVerificationHeader, buffers *buffers) error { verificationHeaderOrigin := verify.GetOrigin() metaOrigin := meta.GetOrigin() - stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buf) + stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buffers) if err != nil { return err } @@ -51,23 +64,40 @@ func verifyServiceRequestRecursive(body stableMarshaler, meta *session.RequestMe return nil } - return verifyServiceRequestRecursive(body, metaOrigin, verificationHeaderOrigin, buf) + return verifyServiceRequestRecursive(body, metaOrigin, verificationHeaderOrigin, buffers) } -func verifyMessageParts(body, meta, originHeader stableMarshaler, hasOriginHeader bool, sigProvider signatureProvider, buf []byte) (stop bool, err error) { - if err := verifyServiceMessagePart(meta, sigProvider.GetMetaSignature, buf); err != nil { - return false, fmt.Errorf("could not verify meta header: %w", err) +func verifyMessageParts(body, meta, originHeader stableMarshaler, hasOriginHeader bool, sigProvider signatureProvider, buffers *buffers) (stop bool, err error) { + eg := &errgroup.Group{} + + eg.Go(func() error { + if err := verifyServiceMessagePart(meta, sigProvider.GetMetaSignature, buffers.Meta); err != nil { + return fmt.Errorf("could not verify meta header: %w", err) + } + return nil + }) + + eg.Go(func() error { + if err := verifyServiceMessagePart(originHeader, sigProvider.GetOriginSignature, buffers.Header); err != nil { + return fmt.Errorf("could not verify origin of verification header: %w", err) + } + return nil + }) + + if !hasOriginHeader { + eg.Go(func() error { + if err := verifyServiceMessagePart(body, sigProvider.GetBodySignature, buffers.Body); err != nil { + return fmt.Errorf("could not verify body: %w", err) + } + return nil + }) } - if err := verifyServiceMessagePart(originHeader, sigProvider.GetOriginSignature, buf); err != nil { - return false, fmt.Errorf("could not verify origin of verification header: %w", err) + if err := eg.Wait(); err != nil { + return false, err } if !hasOriginHeader { - if err := verifyServiceMessagePart(body, sigProvider.GetBodySignature, buf); err != nil { - return false, fmt.Errorf("could not verify body: %w", err) - } - return true, nil } @@ -82,16 +112,15 @@ func verifyServiceResponse(v serviceResponse) error { meta := v.GetMetaHeader() verificationHeader := v.GetVerificationHeader() body := serviceMessageBody(v) - size := collection.Max(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) - buf := make([]byte, 0, size) - return verifyServiceResponseRecursive(body, meta, verificationHeader, buf) + buffers := createBuffers(body.StableSize(), meta.StableSize(), verificationHeader.StableSize()) + return verifyServiceResponseRecursive(body, meta, verificationHeader, buffers) } -func verifyServiceResponseRecursive(body stableMarshaler, meta *session.ResponseMetaHeader, verify *session.ResponseVerificationHeader, buf []byte) error { +func verifyServiceResponseRecursive(body stableMarshaler, meta *session.ResponseMetaHeader, verify *session.ResponseVerificationHeader, buffers *buffers) error { verificationHeaderOrigin := verify.GetOrigin() metaOrigin := meta.GetOrigin() - stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buf) + stop, err := verifyMessageParts(body, meta, verificationHeaderOrigin, verificationHeaderOrigin != nil, verify, buffers) if err != nil { return err } @@ -99,7 +128,7 @@ func verifyServiceResponseRecursive(body stableMarshaler, meta *session.Response return nil } - return verifyServiceResponseRecursive(body, metaOrigin, verificationHeaderOrigin, buf) + return verifyServiceResponseRecursive(body, metaOrigin, verificationHeaderOrigin, buffers) } func verifyServiceMessagePart(part stableMarshaler, sigRdr func() *refs.Signature, buf []byte) error { diff --git a/util/collection/slice.go b/util/collection/slice.go deleted file mode 100644 index 436f28c..0000000 --- a/util/collection/slice.go +++ /dev/null @@ -1,17 +0,0 @@ -package collection - -import "golang.org/x/exp/constraints" - -// Max finds maximum value of items. -func Max[T constraints.Ordered](items ...T) T { - if len(items) == 0 { - panic("failed to get max value: empty slice") - } - result := items[0] - for i := 1; i < len(items); i++ { - if items[i] > result { - result = items[i] - } - } - return result -} -- 2.45.2 From 1d97ed1b9563f6839c2a70aa1a5f1ceccfffd8f7 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Tue, 28 Feb 2023 18:52:59 +0300 Subject: [PATCH 6/6] [#3] .github: Fix github action Fix test run version Signed-off-by: Dmitrii Stepanov --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 48e770c..cfd4c6c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go: [ '1.17.x', '1.18.x', '1.19.x' ] + go: [ '1.18.x', '1.19.x', '1.20.x' ] steps: - name: Setup go uses: actions/setup-go@v3 -- 2.45.2