From cc011f663af8b9eea7cb1c5dbb20fdea004076e4 Mon Sep 17 00:00:00 2001 From: unglaublicherdude Date: Wed, 21 Feb 2024 11:44:10 +0100 Subject: [PATCH 1/2] adds for stream method --- golang/vaas/go.mod | 6 +- golang/vaas/go.sum | 28 +++--- .../messages/verdict_request_for_stream.go | 45 +++++++++ .../pkg/messages/verdict_request_interface.go | 5 +- golang/vaas/pkg/vaas/vaas.go | 96 ++++++++++++++++--- golang/vaas/pkg/vaas/vaas_test.go | 38 +++++++- 6 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 golang/vaas/pkg/messages/verdict_request_for_stream.go diff --git a/golang/vaas/go.mod b/golang/vaas/go.mod index 957dcc01..d635ce08 100644 --- a/golang/vaas/go.mod +++ b/golang/vaas/go.mod @@ -10,13 +10,16 @@ require ( ) require ( - github.com/Noooste/fhttp v1.0.6 // indirect + github.com/Noooste/fhttp v1.0.8 // indirect github.com/Noooste/utls v1.2.5 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/onsi/ginkgo/v2 v2.15.0 // indirect + github.com/onsi/gomega v1.30.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/quic-go v0.41.0 // indirect github.com/refraction-networking/utls v1.6.2 // indirect @@ -24,5 +27,6 @@ require ( golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/golang/vaas/go.sum b/golang/vaas/go.sum index 0a9d9d6a..e18395e2 100644 --- a/golang/vaas/go.sum +++ b/golang/vaas/go.sum @@ -1,5 +1,5 @@ -github.com/Noooste/fhttp v1.0.6 h1:E1u8b+GMhRZSuoINNpiXjE1MHUdZMIcs/g4HEjapWLg= -github.com/Noooste/fhttp v1.0.6/go.mod h1:7rH441v5BuOAQ60LPj7Uwinew1bmv7A0q8DryN/YA6s= +github.com/Noooste/fhttp v1.0.8 h1:iLSM75L7SInEirfdvwJUrUd/Y3AeF1LwpMuOQMM0zEg= +github.com/Noooste/fhttp v1.0.8/go.mod h1:CMVxKOhNheqJN5HYE4Rlvz2SRdV8Uv7YWmi6OwmB/Bk= github.com/Noooste/utls v1.2.5 h1:x7ye66hXXeeMju2redAUSQ5IZBVpTMqX0/C5dHPLpUA= github.com/Noooste/utls v1.2.5/go.mod h1:MRUEmRiDO6ORKziZ2ObNwMjxy0vRviJ91JF1qVa0loM= github.com/Noooste/websocket v1.0.3 h1:drW7tvZ3YqzqI9wApnaH1Q0syFMXO7gbLlsBWjZvMNA= @@ -11,14 +11,14 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -29,10 +29,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -52,8 +52,8 @@ golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/golang/vaas/pkg/messages/verdict_request_for_stream.go b/golang/vaas/pkg/messages/verdict_request_for_stream.go new file mode 100644 index 00000000..7c0c4d22 --- /dev/null +++ b/golang/vaas/pkg/messages/verdict_request_for_stream.go @@ -0,0 +1,45 @@ +// Package messages provides structures for handling communication messages between the client and the VaaS server. +package messages + +import ( + "github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/options" + "github.com/google/uuid" +) + +// VerdictRequestForURL is a specific implementation of VerdictRequest used for URL analysis requests. +type verdictRequestForStream struct { + Kind Kind `json:"kind" default:"VerdictRequestForStream"` + GUID string `json:"guid"` + SessionID string `json:"session_id"` + VerdictRequestAttributes VerdictRequestAttributes `json:"verdict_request_attributes"` + UseCache bool `json:"use_cache"` + UseHashLookup bool `json:"use_shed"` +} + +// GetGUID returns the GUID of the verdictRequestForURL. +func (r verdictRequestForStream) GetGUID() string { + return r.GUID +} + +// NewVerdictRequestForURL creates a new verdictRequestForURL instance. +func NewVerdictRequestForStream(sessionID string, options options.VaasOptions) VerdictRequest { + return verdictRequestForStream{ + Kind: VerdictRequestForStreamKind, + SessionID: sessionID, + GUID: uuid.New().String(), + UseCache: options.UseCache, + UseHashLookup: options.UseHashLookup, + } +} + +// NewVerdictRequestForURLWithAttributes creates a new verdictRequestForURL instance with attributes. +func NewVerdictRequestForStreamWithAttributes(sessionID string, options options.VaasOptions, attributes VerdictRequestAttributes) VerdictRequest { + return verdictRequestForStream{ + Kind: VerdictRequestForStreamKind, + SessionID: sessionID, + GUID: uuid.New().String(), + UseCache: options.UseCache, + UseHashLookup: options.UseHashLookup, + VerdictRequestAttributes: attributes, + } +} diff --git a/golang/vaas/pkg/messages/verdict_request_interface.go b/golang/vaas/pkg/messages/verdict_request_interface.go index a243b6a3..842ecc40 100644 --- a/golang/vaas/pkg/messages/verdict_request_interface.go +++ b/golang/vaas/pkg/messages/verdict_request_interface.go @@ -6,8 +6,9 @@ type Kind string // VerdictRequestKind and VerdictRequestForURLKind are kinds of VerdictRequest. const ( - VerdictRequestKind Kind = "VerdictRequest" - VerdictRequestForURLKind Kind = "VerdictRequestForUrl" + VerdictRequestKind Kind = "VerdictRequest" + VerdictRequestForURLKind Kind = "VerdictRequestForUrl" + VerdictRequestForStreamKind Kind = "VerdictRequestForStream" ) // VerdictRequest is an interface for various types of verdict requests. diff --git a/golang/vaas/pkg/vaas/vaas.go b/golang/vaas/pkg/vaas/vaas.go index 514d0b9c..e5e368bd 100644 --- a/golang/vaas/pkg/vaas/vaas.go +++ b/golang/vaas/pkg/vaas/vaas.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strings" "io" "log" @@ -30,6 +31,7 @@ import ( type Vaas interface { Connect(ctx context.Context, auth authenticator.Authenticator) (termChan <-chan error, err error) ForUrl(ctx context.Context, uri string) (msg.VaasVerdict, error) + ForStream(ctx context.Context, stream io.Reader, contentLength int64) (msg.VaasVerdict, error) ForSha256(ctx context.Context, sha256 string) (msg.VaasVerdict, error) ForFile(ctx context.Context, path string) (msg.VaasVerdict, error) ForFileInMemory(ctx context.Context, file io.Reader) (msg.VaasVerdict, error) @@ -355,6 +357,62 @@ func (v *vaas) ForUrl(ctx context.Context, url string) (msg.VaasVerdict, error) }, nil } +// ForUrl sends an analysis request for a file URL to the Vaas server and returns the verdict. +// The analysis can be canceled using the provided context. +// +// Example usage: +// +// vaasClient := vaas.New(options, "wss://example.authentication.endpoint") +// ctx := context.Background() +// verdict, err := vaasClient.ForStream(ctx, stream) +// if err != nil { +// log.Fatalf("Failed to get verdict: %v", err) +// } +// fmt.Printf("Verdict: %s\n", verdict.Verdict) +// fmt.Printf("SHA256: %s\n", verdict.Sha256) +func (v *vaas) ForStream(ctx context.Context, stream io.Reader, contentLength int64) (msg.VaasVerdict, error) { + if v.sessionID == "" { + return msg.VaasVerdict{}, errors.New("invalid operation") + } + + request := msg.NewVerdictRequestForStream(v.sessionID, v.options) + + responseChan := v.openRequest(request) + defer v.closeRequest(request) + + var response msg.VerdictResponse + select { + case response = <-responseChan: + case <-ctx.Done(): + return msg.VaasVerdict{}, ctx.Err() + } + + if response.Verdict != "" && response.Verdict != msg.Unknown { + return msg.VaasVerdict{}, errors.New("server returned verdict without receiving content") + } + + if len(strings.TrimSpace(response.UploadToken)) == 0 { + return msg.VaasVerdict{}, errors.New("verdictResponse missing UploadToken for stream upload") + } + + if len(strings.TrimSpace(response.URL)) == 0 { + return msg.VaasVerdict{}, errors.New("verdictResponse missing URL for stream upload") + } + + if err := v.uploadFile(stream, contentLength, response.URL, response.UploadToken); err != nil { + return msg.VaasVerdict{ + Verdict: msg.Error, + ErrMsg: err.Error(), + }, err + } + response = <-responseChan + + return msg.VaasVerdict{ + Verdict: response.Verdict, + Sha256: response.Sha256, + }, nil +} + func (v *vaas) authenticate(ctx context.Context, auth authenticator.Authenticator) error { v.waitAuthenticated.Add(1) defer v.waitAuthenticated.Done() @@ -419,7 +477,7 @@ func (v *vaas) forFileWithSha(ctx context.Context, data io.Reader, sha256 string } if response.Verdict == msg.Unknown { - if err := v.uploadFile(data, response.URL, response.UploadToken); err != nil { + if err := v.uploadFile(data, 0, response.URL, response.UploadToken); err != nil { return msg.VaasVerdict{ Verdict: msg.Error, Sha256: sha256, @@ -461,24 +519,36 @@ func (v *vaas) closeRequest(request msg.VerdictRequest) { v.openRequestsMutex.Unlock() } -func (v *vaas) uploadFile(file io.Reader, url string, token string) error { +func (v *vaas) uploadFile(file io.Reader, contentLength int64, url string, token string) error { req, err := http.NewRequest(http.MethodPut, url, file) if err != nil { return err } - // VAAS requires a set Content-Length. - // Here can add support for various io.Reader, which are not supported by the http package. - if req.ContentLength == 0 { - switch t := file.(type) { - case *os.File: - var info os.FileInfo - if info, err = t.Stat(); err != nil { - return err + if contentLength > 0 { + req.ContentLength = contentLength + } else { + // VAAS requires a set Content-Length. + // Here can add support for various io.Reader, which are not supported by the http package. + if req.ContentLength == 0 { + switch t := file.(type) { + case *os.File: + var info os.FileInfo + if info, err = t.Stat(); err != nil { + return err + } + req.ContentLength = info.Size() + case io.ReadCloser: + if s, ok := file.(io.Seeker); ok { + if size, err := s.Seek(0, io.SeekEnd); err == nil { + if _, err = s.Seek(0, io.SeekStart); err == nil { + req.ContentLength = size + } + } + } + default: + return fmt.Errorf("unsupported reader (%T), can not determine content length", file) } - req.ContentLength = info.Size() - default: - return fmt.Errorf("unsupported reader (%T), can not determine content length", file) } } diff --git a/golang/vaas/pkg/vaas/vaas_test.go b/golang/vaas/pkg/vaas/vaas_test.go index 09c7a8e3..2a26e9b4 100644 --- a/golang/vaas/pkg/vaas/vaas_test.go +++ b/golang/vaas/pkg/vaas/vaas_test.go @@ -8,6 +8,7 @@ import ( "io" "log" "math/rand" + "net/http" "os" "path/filepath" "strings" @@ -16,8 +17,8 @@ import ( "github.com/joho/godotenv" "github.com/stretchr/testify/assert" - msg "github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/messages" "github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/authenticator" + msg "github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/messages" "github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/options" ) @@ -286,6 +287,41 @@ func TestVaas_ForFile_And_ForFileInMemory(t *testing.T) { } } +func TestVaas_ForStream_WithStreamFromString(t *testing.T) { + eicarReader := strings.NewReader("X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*") + fixture := new(testFixture) + VaasClient := fixture.setUp(t) + defer fixture.tearDown(t) + + verdict, err := VaasClient.ForStream(context.Background(), eicarReader, eicarReader.Size()) + + if err != nil { + t.Fatalf("unexpected error - %v", err) + } + + if verdict.Verdict != msg.Malicious { + t.Errorf("verdict should be %v, got %v", msg.Malicious, verdict.Verdict) + } +} + +func TestVaas_ForStream_WithStreamFromUrl(t *testing.T) { + response, _ := http.Get("https://secure.eicar.org/eicar.com.txt") + + fixture := new(testFixture) + VaasClient := fixture.setUp(t) + defer fixture.tearDown(t) + + verdict, err := VaasClient.ForStream(context.Background(), response.Body, response.ContentLength) + + if err != nil { + t.Fatalf("unexpected error - %v", err) + } + + if verdict.Verdict != msg.Malicious { + t.Errorf("verdict should be %v, got %v", msg.Malicious, verdict.Verdict) + } +} + func TestVaas_ForUrl(t *testing.T) { const ( cleanURL string = "https://random-data-api.com/api/v2/beers" From 9968bfcd9b683c1dbd0e2992aa73efb68cae4d46 Mon Sep 17 00:00:00 2001 From: unglaublicherdude Date: Thu, 22 Feb 2024 11:00:39 +0100 Subject: [PATCH 2/2] fix comment --- golang/vaas/pkg/vaas/vaas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/golang/vaas/pkg/vaas/vaas.go b/golang/vaas/pkg/vaas/vaas.go index e5e368bd..34f29417 100644 --- a/golang/vaas/pkg/vaas/vaas.go +++ b/golang/vaas/pkg/vaas/vaas.go @@ -357,7 +357,7 @@ func (v *vaas) ForUrl(ctx context.Context, url string) (msg.VaasVerdict, error) }, nil } -// ForUrl sends an analysis request for a file URL to the Vaas server and returns the verdict. +// ForStream sends an analysis request for a file stream to the Vaas server and returns the verdict. // The analysis can be canceled using the provided context. // // Example usage: