mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
support hashed credentials
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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))
|
||||||
|
}
|
||||||
|
@@ -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})
|
||||||
|
Reference in New Issue
Block a user