support hashed credentials

This commit is contained in:
aler9
2020-12-31 19:27:41 +01:00
parent b2f56c9814
commit 9bd587e576
4 changed files with 178 additions and 50 deletions

View File

@@ -9,39 +9,70 @@ import (
"github.com/aler9/gortsplib/pkg/headers" "github.com/aler9/gortsplib/pkg/headers"
) )
var casesAuth = []struct { func TestAuth(t *testing.T) {
name string for _, c1 := range []struct {
methods []headers.AuthMethod name string
}{ methods []headers.AuthMethod
{ }{
"basic", {
[]headers.AuthMethod{headers.AuthBasic}, "basic",
}, []headers.AuthMethod{headers.AuthBasic},
{ },
"digest", {
[]headers.AuthMethod{headers.AuthDigest}, "digest",
}, []headers.AuthMethod{headers.AuthDigest},
{ },
"both", {
[]headers.AuthMethod{headers.AuthBasic, 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) { t.Run(c1.name+"_"+conf, func(t *testing.T) {
for _, c := range casesAuth { va := NewValidator("testuser", "testpass", c1.methods)
t.Run(c.name, func(t *testing.T) { wwwAuthenticate := va.GenerateHeader()
va := NewValidator("testuser", "testpass", c.methods)
wwwAuthenticate := va.GenerateHeader()
se, err := NewSender(wwwAuthenticate, "testuser", "testpass") se, err := NewSender(wwwAuthenticate,
require.NoError(t, err) func() string {
authorization := se.GenerateHeader(base.Announce, if conf == "wronguser" {
base.MustParseURL("rtsp://myhost/mypath")) 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, err = va.ValidateHeader(authorization, base.Announce,
base.MustParseURL("rtsp://myhost/mypath")) base.MustParseURL("rtsp://myhost/mypath"))
require.NoError(t, err)
}) 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", "rtsp://myhost/mypath/test?testing/trackID=0",
}, },
} { } {
se := NewValidator("testuser", "testpass", va := NewValidator("testuser", "testpass",
[]headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}) []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest})
va, err := NewSender(se.GenerateHeader(), "testuser", "testpass") se, err := NewSender(va.GenerateHeader(), "testuser", "testpass")
require.NoError(t, err) require.NoError(t, err)
authorization := va.GenerateHeader(base.Announce, authorization := se.GenerateHeader(base.Announce,
base.MustParseURL(ca.clientURL)) base.MustParseURL(ca.clientURL))
err = se.ValidateHeader(authorization, base.Announce, err = va.ValidateHeader(authorization, base.Announce,
base.MustParseURL(ca.serverURL)) base.MustParseURL(ca.serverURL))
require.NoError(t, err) 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)
}
})
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/aler9/gortsplib/pkg/headers" "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 { type Sender struct {
user string user string
pass string pass string
@@ -19,7 +19,7 @@ type Sender struct {
} }
// NewSender allocates a Sender with the WWW-Authenticate header provided by // 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) { func NewSender(v base.HeaderValue, user string, pass string) (*Sender, error) {
// prefer digest // prefer digest
if headerAuthDigest := func() string { if headerAuthDigest := func() string {

View File

@@ -2,6 +2,8 @@ package auth
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex" "encoding/hex"
) )
@@ -10,3 +12,9 @@ func md5Hex(in string) string {
h.Write([]byte(in)) h.Write([]byte(in))
return hex.EncodeToString(h.Sum(nil)) 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))
}

View File

@@ -11,13 +11,15 @@ import (
"github.com/aler9/gortsplib/pkg/headers" "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 { type Validator struct {
user string user string
pass string userHashed bool
methods []headers.AuthMethod pass string
realm string passHashed bool
nonce string methods []headers.AuthMethod
realm string
nonce string
} }
// NewValidator allocates a Validator. // 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} 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) nonceByts := make([]byte, 16)
rand.Read(nonceByts) rand.Read(nonceByts)
nonce := hex.EncodeToString(nonceByts) nonce := hex.EncodeToString(nonceByts)
return &Validator{ return &Validator{
user: user, user: user,
pass: pass, userHashed: userHashed,
methods: methods, pass: pass,
realm: "IPCAM", passHashed: passHashed,
nonce: nonce, 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 ") { if strings.HasPrefix(v0, "Basic ") {
inResponse := v0[len("Basic "):] inResponse := v0[len("Basic "):]
response := base64.StdEncoding.EncodeToString([]byte(va.user + ":" + va.pass)) tmp, err := base64.StdEncoding.DecodeString(inResponse)
if err != nil {
if inResponse != response {
return fmt.Errorf("wrong response") 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 ") { } else if strings.HasPrefix(v0, "Digest ") {
auth, err := headers.ReadAuth(base.HeaderValue{v0}) auth, err := headers.ReadAuth(base.HeaderValue{v0})