From 9bd587e576f10361325122bfd16adbbe0b963895 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Thu, 31 Dec 2020 19:27:41 +0100 Subject: [PATCH] support hashed credentials --- pkg/auth/package_test.go | 139 +++++++++++++++++++++++++++++---------- pkg/auth/sender.go | 4 +- pkg/auth/utils.go | 8 +++ pkg/auth/validator.go | 77 ++++++++++++++++++---- 4 files changed, 178 insertions(+), 50 deletions(-) diff --git a/pkg/auth/package_test.go b/pkg/auth/package_test.go index 8e4a6a21..9f4463f0 100644 --- a/pkg/auth/package_test.go +++ b/pkg/auth/package_test.go @@ -9,39 +9,70 @@ import ( "github.com/aler9/gortsplib/pkg/headers" ) -var casesAuth = []struct { - name string - methods []headers.AuthMethod -}{ - { - "basic", - []headers.AuthMethod{headers.AuthBasic}, - }, - { - "digest", - []headers.AuthMethod{headers.AuthDigest}, - }, - { - "both", - []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}, - }, -} +func TestAuth(t *testing.T) { + for _, c1 := range []struct { + name string + methods []headers.AuthMethod + }{ + { + "basic", + []headers.AuthMethod{headers.AuthBasic}, + }, + { + "digest", + []headers.AuthMethod{headers.AuthDigest}, + }, + { + "both", + []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}, + }, + } { + for _, conf := range []string{ + "nofail", + "wronguser", + "wrongpass", + "wrongurl", + } { + if conf == "wrongurl" && c1.name == "basic" { + continue + } -func TestAuthMethods(t *testing.T) { - for _, c := range casesAuth { - t.Run(c.name, func(t *testing.T) { - va := NewValidator("testuser", "testpass", c.methods) - wwwAuthenticate := va.GenerateHeader() + t.Run(c1.name+"_"+conf, func(t *testing.T) { + va := NewValidator("testuser", "testpass", c1.methods) + wwwAuthenticate := va.GenerateHeader() - se, err := NewSender(wwwAuthenticate, "testuser", "testpass") - require.NoError(t, err) - authorization := se.GenerateHeader(base.Announce, - base.MustParseURL("rtsp://myhost/mypath")) + se, err := NewSender(wwwAuthenticate, + func() string { + if conf == "wronguser" { + return "test1user" + } + return "testuser" + }(), + func() string { + if conf == "wrongpass" { + return "test1pass" + } + return "testpass" + }()) + require.NoError(t, err) + authorization := se.GenerateHeader(base.Announce, + base.MustParseURL(func() string { + if conf == "wrongurl" { + return "rtsp://myhost/my1path" + } + return "rtsp://myhost/mypath" + }())) - err = va.ValidateHeader(authorization, base.Announce, - base.MustParseURL("rtsp://myhost/mypath")) - require.NoError(t, err) - }) + err = va.ValidateHeader(authorization, base.Announce, + base.MustParseURL("rtsp://myhost/mypath")) + + if conf != "nofail" { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } } } @@ -59,16 +90,56 @@ func TestAuthVLC(t *testing.T) { "rtsp://myhost/mypath/test?testing/trackID=0", }, } { - se := NewValidator("testuser", "testpass", + va := NewValidator("testuser", "testpass", []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}) - va, err := NewSender(se.GenerateHeader(), "testuser", "testpass") + se, err := NewSender(va.GenerateHeader(), "testuser", "testpass") require.NoError(t, err) - authorization := va.GenerateHeader(base.Announce, + authorization := se.GenerateHeader(base.Announce, base.MustParseURL(ca.clientURL)) - err = se.ValidateHeader(authorization, base.Announce, + err = va.ValidateHeader(authorization, base.Announce, base.MustParseURL(ca.serverURL)) require.NoError(t, err) } } + +func TestAuthHashed(t *testing.T) { + for _, conf := range []string{ + "nofail", + "wronguser", + "wrongpass", + } { + t.Run(conf, func(t *testing.T) { + se := NewValidator("sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=", + "sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w=", + []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}) + + va, err := NewSender(se.GenerateHeader(), + func() string { + if conf == "wronguser" { + return "test1user" + } + return "testuser" + }(), + func() string { + if conf == "wrongpass" { + return "test1pass" + } + return "testpass" + }()) + require.NoError(t, err) + authorization := va.GenerateHeader(base.Announce, + base.MustParseURL("rtsp://myhost/mypath")) + + err = se.ValidateHeader(authorization, base.Announce, + base.MustParseURL("rtsp://myhost/mypath")) + + if conf != "nofail" { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/auth/sender.go b/pkg/auth/sender.go index bca56313..859a7276 100644 --- a/pkg/auth/sender.go +++ b/pkg/auth/sender.go @@ -9,7 +9,7 @@ import ( "github.com/aler9/gortsplib/pkg/headers" ) -// Sender is an object that allows a client to send credentials to a server. +// Sender allows to generate credentials for a Validator. type Sender struct { user string pass string @@ -19,7 +19,7 @@ type Sender struct { } // NewSender allocates a Sender with the WWW-Authenticate header provided by -// the server and a set of credentials. +// a Validator and a set of credentials. func NewSender(v base.HeaderValue, user string, pass string) (*Sender, error) { // prefer digest if headerAuthDigest := func() string { diff --git a/pkg/auth/utils.go b/pkg/auth/utils.go index 238ab753..29383cd6 100644 --- a/pkg/auth/utils.go +++ b/pkg/auth/utils.go @@ -2,6 +2,8 @@ package auth import ( "crypto/md5" + "crypto/sha256" + "encoding/base64" "encoding/hex" ) @@ -10,3 +12,9 @@ func md5Hex(in string) string { h.Write([]byte(in)) return hex.EncodeToString(h.Sum(nil)) } + +func sha256Base64(in string) string { + h := sha256.New() + h.Write([]byte(in)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/pkg/auth/validator.go b/pkg/auth/validator.go index c6a05502..539403f2 100644 --- a/pkg/auth/validator.go +++ b/pkg/auth/validator.go @@ -11,13 +11,15 @@ import ( "github.com/aler9/gortsplib/pkg/headers" ) -// Validator allows a server to validate some credentials sent by a client. +// Validator allows to validate some credentials generated by a Sender. type Validator struct { - user string - pass string - methods []headers.AuthMethod - realm string - nonce string + user string + userHashed bool + pass string + passHashed bool + methods []headers.AuthMethod + realm string + nonce string } // NewValidator allocates a Validator. @@ -27,16 +29,39 @@ func NewValidator(user string, pass string, methods []headers.AuthMethod) *Valid methods = []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest} } + userHashed := false + if strings.HasPrefix(user, "plain:") { + user = strings.TrimPrefix(user, "plain:") + } else if strings.HasPrefix(user, "sha256:") { + user = strings.TrimPrefix(user, "sha256:") + userHashed = true + } + + passHashed := false + if strings.HasPrefix(pass, "plain:") { + pass = strings.TrimPrefix(pass, "plain:") + } else if strings.HasPrefix(pass, "sha256:") { + pass = strings.TrimPrefix(pass, "sha256:") + passHashed = true + } + + // if credentials are hashed, only basic auth is supported + if userHashed || passHashed { + methods = []headers.AuthMethod{headers.AuthBasic} + } + nonceByts := make([]byte, 16) rand.Read(nonceByts) nonce := hex.EncodeToString(nonceByts) return &Validator{ - user: user, - pass: pass, - methods: methods, - realm: "IPCAM", - nonce: nonce, + user: user, + userHashed: userHashed, + pass: pass, + passHashed: passHashed, + methods: methods, + realm: "IPCAM", + nonce: nonce, } } @@ -78,11 +103,35 @@ func (va *Validator) ValidateHeader(v base.HeaderValue, method base.Method, ur * if strings.HasPrefix(v0, "Basic ") { inResponse := v0[len("Basic "):] - response := base64.StdEncoding.EncodeToString([]byte(va.user + ":" + va.pass)) - - if inResponse != response { + tmp, err := base64.StdEncoding.DecodeString(inResponse) + if err != nil { return fmt.Errorf("wrong response") } + tmp2 := strings.Split(string(tmp), ":") + if len(tmp2) != 2 { + return fmt.Errorf("wrong response") + } + user, pass := tmp2[0], tmp2[1] + + if !va.userHashed { + if user != va.user { + return fmt.Errorf("wrong response") + } + } else { + if sha256Base64(user) != va.user { + return fmt.Errorf("wrong response") + } + } + + if !va.passHashed { + if pass != va.pass { + return fmt.Errorf("wrong response") + } + } else { + if sha256Base64(pass) != va.pass { + return fmt.Errorf("wrong response") + } + } } else if strings.HasPrefix(v0, "Digest ") { auth, err := headers.ReadAuth(base.HeaderValue{v0})