mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
fix authentication when algorithm field is not supported (#558)
(https://github.com/bluenviron/mediamtx/issues/3116) This fixes authentication issues with some TP-LINK cameras.
This commit is contained in:
@@ -1705,7 +1705,7 @@ func TestClientPlayRedirect(t *testing.T) {
|
|||||||
err2 = conn.WriteResponse(&base.Response{
|
err2 = conn.WriteResponse(&base.Response{
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"WWW-Authenticate": headers.Authenticate{
|
"WWW-Authenticate": headers.Authenticate{
|
||||||
Method: headers.AuthDigestMD5,
|
Method: headers.AuthMethodDigest,
|
||||||
Realm: authRealm,
|
Realm: authRealm,
|
||||||
Nonce: authNonce,
|
Nonce: authNonce,
|
||||||
Opaque: &authOpaque,
|
Opaque: &authOpaque,
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustParseURL(s string) *base.URL {
|
func mustParseURL(s string) *base.URL {
|
||||||
@@ -17,22 +16,22 @@ func mustParseURL(s string) *base.URL {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestCombined(t *testing.T) {
|
||||||
for _, c1 := range []struct {
|
for _, c1 := range []struct {
|
||||||
name string
|
name string
|
||||||
methods []headers.AuthMethod
|
methods []ValidateMethod
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"basic",
|
"basic",
|
||||||
[]headers.AuthMethod{headers.AuthBasic},
|
[]ValidateMethod{ValidateMethodBasic},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"digest md5",
|
"digest md5",
|
||||||
[]headers.AuthMethod{headers.AuthDigestMD5},
|
[]ValidateMethod{ValidateMethodDigestMD5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"digest sha256",
|
"digest sha256",
|
||||||
[]headers.AuthMethod{headers.AuthDigestSHA256},
|
[]ValidateMethod{ValidateMethodSHA256},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"all",
|
"all",
|
||||||
@@ -93,38 +92,3 @@ func TestAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthVLC(t *testing.T) {
|
|
||||||
for _, ca := range []struct {
|
|
||||||
baseURL string
|
|
||||||
mediaURL string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"rtsp://myhost/mypath/",
|
|
||||||
"rtsp://myhost/mypath/trackID=0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rtsp://myhost/mypath/test?testing/",
|
|
||||||
"rtsp://myhost/mypath/test?testing/trackID=0",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
nonce, err := GenerateNonce()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
se, err := NewSender(
|
|
||||||
GenerateWWWAuthenticate(nil, "IPCAM", nonce),
|
|
||||||
"testuser",
|
|
||||||
"testpass")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req := &base.Request{
|
|
||||||
Method: base.Setup,
|
|
||||||
URL: mustParseURL(ca.baseURL),
|
|
||||||
}
|
|
||||||
se.AddAuthorization(req)
|
|
||||||
req.URL = mustParseURL(ca.mediaURL)
|
|
||||||
|
|
||||||
err = Validate(req, "testuser", "testpass", nil, "IPCAM", nonce)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -7,63 +7,41 @@ import (
|
|||||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func findAuthenticateHeader(auths []headers.Authenticate, method headers.AuthMethod) *headers.Authenticate {
|
|
||||||
for _, auth := range auths {
|
|
||||||
if auth.Method == method {
|
|
||||||
return &auth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickAuthenticateHeader(auths []headers.Authenticate) (*headers.Authenticate, error) {
|
|
||||||
if auth := findAuthenticateHeader(auths, headers.AuthDigestSHA256); auth != nil {
|
|
||||||
return auth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth := findAuthenticateHeader(auths, headers.AuthDigestMD5); auth != nil {
|
|
||||||
return auth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth := findAuthenticateHeader(auths, headers.AuthBasic); auth != nil {
|
|
||||||
return auth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("no authentication methods available")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sender allows to send credentials.
|
// Sender allows to send credentials.
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
authenticateHeader *headers.Authenticate
|
authHeader *headers.Authenticate
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSender allocates a Sender.
|
// NewSender allocates a Sender.
|
||||||
// It requires a WWW-Authenticate header (provided by the server)
|
// It requires a WWW-Authenticate header (provided by the server)
|
||||||
// and a set of credentials.
|
// and a set of credentials.
|
||||||
func NewSender(vals base.HeaderValue, user string, pass string) (*Sender, error) {
|
func NewSender(wwwAuth base.HeaderValue, user string, pass string) (*Sender, error) {
|
||||||
var auths []headers.Authenticate //nolint:prealloc
|
var bestAuthHeader *headers.Authenticate
|
||||||
|
|
||||||
for _, v := range vals {
|
for _, v := range wwwAuth {
|
||||||
var auth headers.Authenticate
|
var auth headers.Authenticate
|
||||||
err := auth.Unmarshal(base.HeaderValue{v})
|
err := auth.Unmarshal(base.HeaderValue{v})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // ignore unrecognized headers
|
continue // ignore unrecognized headers
|
||||||
}
|
}
|
||||||
|
|
||||||
auths = append(auths, auth)
|
if bestAuthHeader == nil ||
|
||||||
|
(auth.Algorithm != nil && *auth.Algorithm == headers.AuthAlgorithmSHA256) ||
|
||||||
|
(bestAuthHeader.Method == headers.AuthMethodBasic) {
|
||||||
|
bestAuthHeader = &auth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auth, err := pickAuthenticateHeader(auths)
|
if bestAuthHeader == nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("no authentication methods available")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Sender{
|
return &Sender{
|
||||||
user: user,
|
user: user,
|
||||||
pass: pass,
|
pass: pass,
|
||||||
authenticateHeader: auth,
|
authHeader: bestAuthHeader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,29 +50,26 @@ func (se *Sender) AddAuthorization(req *base.Request) {
|
|||||||
urStr := req.URL.CloneWithoutCredentials().String()
|
urStr := req.URL.CloneWithoutCredentials().String()
|
||||||
|
|
||||||
h := headers.Authorization{
|
h := headers.Authorization{
|
||||||
Method: se.authenticateHeader.Method,
|
Method: se.authHeader.Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch se.authenticateHeader.Method {
|
if se.authHeader.Method == headers.AuthMethodBasic {
|
||||||
case headers.AuthBasic:
|
|
||||||
h.BasicUser = se.user
|
h.BasicUser = se.user
|
||||||
h.BasicPass = se.pass
|
h.BasicPass = se.pass
|
||||||
|
} else { // digest
|
||||||
case headers.AuthDigestMD5:
|
|
||||||
h.Username = se.user
|
h.Username = se.user
|
||||||
h.Realm = se.authenticateHeader.Realm
|
h.Realm = se.authHeader.Realm
|
||||||
h.Nonce = se.authenticateHeader.Nonce
|
h.Nonce = se.authHeader.Nonce
|
||||||
h.URI = urStr
|
h.URI = urStr
|
||||||
h.Response = md5Hex(md5Hex(se.user+":"+se.authenticateHeader.Realm+":"+se.pass) + ":" +
|
h.Algorithm = se.authHeader.Algorithm
|
||||||
se.authenticateHeader.Nonce + ":" + md5Hex(string(req.Method)+":"+urStr))
|
|
||||||
|
|
||||||
default: // digest SHA-256
|
if se.authHeader.Algorithm == nil || *se.authHeader.Algorithm == headers.AuthAlgorithmMD5 {
|
||||||
h.Username = se.user
|
h.Response = md5Hex(md5Hex(se.user+":"+se.authHeader.Realm+":"+se.pass) + ":" +
|
||||||
h.Realm = se.authenticateHeader.Realm
|
se.authHeader.Nonce + ":" + md5Hex(string(req.Method)+":"+urStr))
|
||||||
h.Nonce = se.authenticateHeader.Nonce
|
} else { // sha256
|
||||||
h.URI = urStr
|
h.Response = sha256Hex(sha256Hex(se.user+":"+se.authHeader.Realm+":"+se.pass) + ":" +
|
||||||
h.Response = sha256Hex(sha256Hex(se.user+":"+se.authenticateHeader.Realm+":"+se.pass) + ":" +
|
se.authHeader.Nonce + ":" + sha256Hex(string(req.Method)+":"+urStr))
|
||||||
se.authenticateHeader.Nonce + ":" + sha256Hex(string(req.Method)+":"+urStr))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Header == nil {
|
if req.Header == nil {
|
||||||
|
@@ -4,8 +4,98 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSender(t *testing.T) {
|
||||||
|
for _, ca := range []struct {
|
||||||
|
name string
|
||||||
|
wwwAuthenticate base.HeaderValue
|
||||||
|
authorization base.HeaderValue
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"basic",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Basic realm=testrealm",
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Basic bXl1c2VyOm15cGFzcw==",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest md5 implicit",
|
||||||
|
base.HeaderValue{
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce"`,
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", response=\"ba6e9cccbfeb38db775378a0a9067ba5\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest md5 explicit",
|
||||||
|
base.HeaderValue{
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce", algorithm="MD5"`,
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", response=\"ba6e9cccbfeb38db775378a0a9067ba5\", " +
|
||||||
|
"algorithm=\"MD5\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest sha256",
|
||||||
|
base.HeaderValue{
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce", algorithm="SHA-256"`,
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", " +
|
||||||
|
"response=\"e298296ce35c9ab79699c8f3f9508944c1be9395e892f8205b6d66f1b8e663ee\", " +
|
||||||
|
"algorithm=\"SHA-256\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple 1",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Basic realm=testrealm",
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce"`,
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", response=\"ba6e9cccbfeb38db775378a0a9067ba5\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple 2",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Basic realm=testrealm",
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce", algorithm="MD5"`,
|
||||||
|
`Digest realm="myrealm", nonce="f49ac6dd0ba708d4becddc9692d1f2ce", algorithm="SHA-256"`,
|
||||||
|
},
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", " +
|
||||||
|
"response=\"e298296ce35c9ab79699c8f3f9508944c1be9395e892f8205b6d66f1b8e663ee\", " +
|
||||||
|
"algorithm=\"SHA-256\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
se, err := NewSender(ca.wwwAuthenticate, "myuser", "mypass")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := &base.Request{
|
||||||
|
Method: base.Setup,
|
||||||
|
URL: mustParseURL("rtsp://myhost/mypath?key=val/trackID=3"),
|
||||||
|
}
|
||||||
|
se.AddAuthorization(req)
|
||||||
|
|
||||||
|
require.Equal(t, ca.authorization, req.Header["Authorization"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FuzzSender(f *testing.F) {
|
func FuzzSender(f *testing.F) {
|
||||||
f.Add(`Invalid`)
|
f.Add(`Invalid`)
|
||||||
f.Add(`Digest`)
|
f.Add(`Digest`)
|
||||||
|
@@ -25,7 +25,7 @@ func sha256Hex(in string) string {
|
|||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(list []headers.AuthMethod, item headers.AuthMethod) bool {
|
func contains(list []ValidateMethod, item ValidateMethod) bool {
|
||||||
for _, i := range list {
|
for _, i := range list {
|
||||||
if i == item {
|
if i == item {
|
||||||
return true
|
return true
|
||||||
@@ -51,17 +51,27 @@ func urlMatches(expected string, received string, isSetup bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateMethod is a validation method.
|
||||||
|
type ValidateMethod int
|
||||||
|
|
||||||
|
// validation methods.
|
||||||
|
const (
|
||||||
|
ValidateMethodBasic ValidateMethod = iota
|
||||||
|
ValidateMethodDigestMD5
|
||||||
|
ValidateMethodSHA256
|
||||||
|
)
|
||||||
|
|
||||||
// Validate validates a request sent by a client.
|
// Validate validates a request sent by a client.
|
||||||
func Validate(
|
func Validate(
|
||||||
req *base.Request,
|
req *base.Request,
|
||||||
user string,
|
user string,
|
||||||
pass string,
|
pass string,
|
||||||
methods []headers.AuthMethod,
|
methods []ValidateMethod,
|
||||||
realm string,
|
realm string,
|
||||||
nonce string,
|
nonce string,
|
||||||
) error {
|
) error {
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = []headers.AuthMethod{headers.AuthDigestSHA256, headers.AuthDigestMD5, headers.AuthBasic}
|
methods = []ValidateMethod{ValidateMethodBasic, ValidateMethodDigestMD5, ValidateMethodSHA256}
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth headers.Authorization
|
var auth headers.Authorization
|
||||||
@@ -71,8 +81,11 @@ func Validate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case (auth.Method == headers.AuthDigestSHA256 && contains(methods, headers.AuthDigestSHA256)) ||
|
case auth.Method == headers.AuthMethodDigest &&
|
||||||
(auth.Method == headers.AuthDigestMD5 && contains(methods, headers.AuthDigestMD5)):
|
(contains(methods, ValidateMethodDigestMD5) &&
|
||||||
|
(auth.Algorithm == nil || *auth.Algorithm == headers.AuthAlgorithmMD5) ||
|
||||||
|
contains(methods, ValidateMethodSHA256) &&
|
||||||
|
auth.Algorithm != nil && *auth.Algorithm == headers.AuthAlgorithmSHA256):
|
||||||
if auth.Nonce != nonce {
|
if auth.Nonce != nonce {
|
||||||
return fmt.Errorf("wrong nonce")
|
return fmt.Errorf("wrong nonce")
|
||||||
}
|
}
|
||||||
@@ -91,19 +104,19 @@ func Validate(
|
|||||||
|
|
||||||
var response string
|
var response string
|
||||||
|
|
||||||
if auth.Method == headers.AuthDigestSHA256 {
|
if auth.Algorithm == nil || *auth.Algorithm == headers.AuthAlgorithmMD5 {
|
||||||
response = sha256Hex(sha256Hex(user+":"+realm+":"+pass) +
|
|
||||||
":" + nonce + ":" + sha256Hex(string(req.Method)+":"+auth.URI))
|
|
||||||
} else {
|
|
||||||
response = md5Hex(md5Hex(user+":"+realm+":"+pass) +
|
response = md5Hex(md5Hex(user+":"+realm+":"+pass) +
|
||||||
":" + nonce + ":" + md5Hex(string(req.Method)+":"+auth.URI))
|
":" + nonce + ":" + md5Hex(string(req.Method)+":"+auth.URI))
|
||||||
|
} else { // sha256
|
||||||
|
response = sha256Hex(sha256Hex(user+":"+realm+":"+pass) +
|
||||||
|
":" + nonce + ":" + sha256Hex(string(req.Method)+":"+auth.URI))
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.Response != response {
|
if auth.Response != response {
|
||||||
return fmt.Errorf("authentication failed")
|
return fmt.Errorf("authentication failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
case auth.Method == headers.AuthBasic && contains(methods, headers.AuthBasic):
|
case auth.Method == headers.AuthMethodBasic && contains(methods, ValidateMethodBasic):
|
||||||
if auth.BasicUser != user {
|
if auth.BasicUser != user {
|
||||||
return fmt.Errorf("authentication failed")
|
return fmt.Errorf("authentication failed")
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,88 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
for _, ca := range []struct {
|
||||||
|
name string
|
||||||
|
authorization base.HeaderValue
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"basic",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Basic bXl1c2VyOm15cGFzcw==",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest md5 implicit",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", response=\"ba6e9cccbfeb38db775378a0a9067ba5\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest md5 explicit",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", response=\"ba6e9cccbfeb38db775378a0a9067ba5\", " +
|
||||||
|
"algorithm=\"MD5\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest sha256",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/trackID=3\", " +
|
||||||
|
"response=\"e298296ce35c9ab79699c8f3f9508944c1be9395e892f8205b6d66f1b8e663ee\", " +
|
||||||
|
"algorithm=\"SHA-256\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest vlc",
|
||||||
|
base.HeaderValue{
|
||||||
|
"Digest username=\"myuser\", realm=\"myrealm\", nonce=\"f49ac6dd0ba708d4becddc9692d1f2ce\", " +
|
||||||
|
"uri=\"rtsp://myhost/mypath?key=val/\", response=\"5ca5ceeca20a05e9a3f49ecde4b42655\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
se, err := NewSender(
|
||||||
|
GenerateWWWAuthenticate([]ValidateMethod{ValidateMethodDigestMD5}, "myrealm", "f49ac6dd0ba708d4becddc9692d1f2ce"),
|
||||||
|
"myuser",
|
||||||
|
"mypass")
|
||||||
|
require.NoError(t, err)
|
||||||
|
req1 := &base.Request{
|
||||||
|
Method: base.Setup,
|
||||||
|
URL: mustParseURL("rtsp://myhost/mypath?key=val/"),
|
||||||
|
}
|
||||||
|
se.AddAuthorization(req1)
|
||||||
|
fmt.Println(req1.Header)
|
||||||
|
|
||||||
|
req := &base.Request{
|
||||||
|
Method: base.Setup,
|
||||||
|
URL: mustParseURL("rtsp://myhost/mypath?key=val/trackID=3"),
|
||||||
|
Header: base.Header{
|
||||||
|
"Authorization": ca.authorization,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = Validate(
|
||||||
|
req,
|
||||||
|
"myuser",
|
||||||
|
"mypass",
|
||||||
|
nil,
|
||||||
|
"myrealm",
|
||||||
|
"f49ac6dd0ba708d4becddc9692d1f2ce")
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FuzzValidate(f *testing.F) {
|
func FuzzValidate(f *testing.F) {
|
||||||
f.Add(`Invalid`)
|
f.Add(`Invalid`)
|
||||||
f.Add(`Digest `)
|
f.Add(`Digest `)
|
||||||
@@ -35,21 +110,3 @@ func FuzzValidate(f *testing.F) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateAdditionalErrors(t *testing.T) {
|
|
||||||
err := Validate(
|
|
||||||
&base.Request{
|
|
||||||
Method: base.Describe,
|
|
||||||
URL: nil,
|
|
||||||
Header: base.Header{
|
|
||||||
"Authorization": base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"myuser",
|
|
||||||
"mypass",
|
|
||||||
[]headers.AuthMethod{headers.AuthDigestMD5},
|
|
||||||
"IPCAM",
|
|
||||||
"abcde",
|
|
||||||
)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
@@ -6,18 +6,44 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GenerateWWWAuthenticate generates a WWW-Authenticate header.
|
// GenerateWWWAuthenticate generates a WWW-Authenticate header.
|
||||||
func GenerateWWWAuthenticate(methods []headers.AuthMethod, realm string, nonce string) base.HeaderValue {
|
func GenerateWWWAuthenticate(methods []ValidateMethod, realm string, nonce string) base.HeaderValue {
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = []headers.AuthMethod{headers.AuthDigestSHA256, headers.AuthDigestMD5, headers.AuthBasic}
|
methods = []ValidateMethod{ValidateMethodBasic, ValidateMethodDigestMD5, ValidateMethodSHA256}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret base.HeaderValue
|
var ret base.HeaderValue
|
||||||
|
|
||||||
for _, m := range methods {
|
for _, m := range methods {
|
||||||
ret = append(ret, headers.Authenticate{
|
var a base.HeaderValue
|
||||||
Method: m,
|
|
||||||
|
switch m {
|
||||||
|
case ValidateMethodBasic:
|
||||||
|
a = headers.Authenticate{
|
||||||
|
Method: headers.AuthMethodBasic,
|
||||||
Realm: realm,
|
Realm: realm,
|
||||||
Nonce: nonce, // used only by digest
|
}.Marshal()
|
||||||
}.Marshal()...)
|
|
||||||
|
case ValidateMethodDigestMD5:
|
||||||
|
aa := headers.AuthAlgorithmMD5
|
||||||
|
a = headers.Authenticate{
|
||||||
|
Method: headers.AuthMethodDigest,
|
||||||
|
Realm: realm,
|
||||||
|
Nonce: nonce,
|
||||||
|
Algorithm: &aa,
|
||||||
|
}.Marshal()
|
||||||
|
|
||||||
|
default: // sha256
|
||||||
|
aa := headers.AuthAlgorithmSHA256
|
||||||
|
a = headers.Authenticate{
|
||||||
|
Method: headers.AuthMethodDigest,
|
||||||
|
Realm: realm,
|
||||||
|
Nonce: nonce,
|
||||||
|
Algorithm: &aa,
|
||||||
|
}.Marshal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = append(ret, a...)
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
@@ -11,34 +11,31 @@ import (
|
|||||||
// AuthMethod is an authentication method.
|
// AuthMethod is an authentication method.
|
||||||
type AuthMethod int
|
type AuthMethod int
|
||||||
|
|
||||||
|
// authentication methods.
|
||||||
const (
|
const (
|
||||||
// AuthBasic is the Basic authentication method
|
AuthMethodBasic AuthMethod = iota
|
||||||
AuthBasic AuthMethod = iota
|
AuthMethodDigest
|
||||||
|
|
||||||
// AuthDigestMD5 is the Digest authentication method with the MD5 hash
|
|
||||||
AuthDigestMD5
|
|
||||||
|
|
||||||
// AuthDigestSHA256 is the Digest authentication method with the SHA-256 hash
|
|
||||||
AuthDigestSHA256
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AuthAlgorithm is a digest algorithm.
|
||||||
|
type AuthAlgorithm int
|
||||||
|
|
||||||
|
// digest algorithms.
|
||||||
const (
|
const (
|
||||||
// AuthDigest is an alias for AuthDigestMD5
|
AuthAlgorithmMD5 AuthAlgorithm = iota
|
||||||
//
|
AuthAlgorithmSHA256
|
||||||
// Deprecated: replaced by AuthDigestMD5
|
|
||||||
AuthDigest = AuthDigestMD5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func algorithmToMethod(v *string) (AuthMethod, error) {
|
func parseAuthAlgorithm(v string) (AuthAlgorithm, error) {
|
||||||
switch {
|
switch {
|
||||||
case v == nil, strings.ToLower(*v) == "md5":
|
case strings.ToLower(v) == "md5":
|
||||||
return AuthDigestMD5, nil
|
return AuthAlgorithmMD5, nil
|
||||||
|
|
||||||
case strings.ToLower(*v) == "sha-256":
|
case strings.ToLower(v) == "sha-256":
|
||||||
return AuthDigestSHA256, nil
|
return AuthAlgorithmSHA256, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("unrecognized algorithm: %v", *v)
|
return 0, fmt.Errorf("unrecognized algorithm: %v", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +59,9 @@ type Authenticate struct {
|
|||||||
|
|
||||||
// stale
|
// stale
|
||||||
Stale *string
|
Stale *string
|
||||||
|
|
||||||
|
// algorithm
|
||||||
|
Algorithm *AuthAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal decodes a WWW-Authenticate header.
|
// Unmarshal decodes a WWW-Authenticate header.
|
||||||
@@ -82,20 +82,18 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
|||||||
}
|
}
|
||||||
method, v0 := v0[:i], v0[i+1:]
|
method, v0 := v0[:i], v0[i+1:]
|
||||||
|
|
||||||
isDigest := false
|
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "Basic":
|
case "Basic":
|
||||||
h.Method = AuthBasic
|
h.Method = AuthMethodBasic
|
||||||
|
|
||||||
case "Digest":
|
case "Digest":
|
||||||
isDigest = true
|
h.Method = AuthMethodDigest
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid method (%s)", method)
|
return fmt.Errorf("invalid method (%s)", method)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDigest {
|
if h.Method == AuthMethodBasic {
|
||||||
kvs, err := keyValParse(v0, ',')
|
kvs, err := keyValParse(v0, ',')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -123,7 +121,6 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
|||||||
|
|
||||||
realmReceived := false
|
realmReceived := false
|
||||||
nonceReceived := false
|
nonceReceived := false
|
||||||
var algorithm *string
|
|
||||||
|
|
||||||
for k, rv := range kvs {
|
for k, rv := range kvs {
|
||||||
v := rv
|
v := rv
|
||||||
@@ -144,18 +141,17 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
|||||||
h.Stale = &v
|
h.Stale = &v
|
||||||
|
|
||||||
case "algorithm":
|
case "algorithm":
|
||||||
algorithm = &v
|
a, err := parseAuthAlgorithm(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.Algorithm = &a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !realmReceived || !nonceReceived {
|
if !realmReceived || !nonceReceived {
|
||||||
return fmt.Errorf("one or more digest fields are missing")
|
return fmt.Errorf("one or more digest fields are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Method, err = algorithmToMethod(algorithm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -163,7 +159,7 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
|||||||
|
|
||||||
// Marshal encodes a WWW-Authenticate header.
|
// Marshal encodes a WWW-Authenticate header.
|
||||||
func (h Authenticate) Marshal() base.HeaderValue {
|
func (h Authenticate) Marshal() base.HeaderValue {
|
||||||
if h.Method == AuthBasic {
|
if h.Method == AuthMethodBasic {
|
||||||
return base.HeaderValue{"Basic " +
|
return base.HeaderValue{"Basic " +
|
||||||
"realm=\"" + h.Realm + "\""}
|
"realm=\"" + h.Realm + "\""}
|
||||||
}
|
}
|
||||||
@@ -178,11 +174,13 @@ func (h Authenticate) Marshal() base.HeaderValue {
|
|||||||
ret += ", stale=\"" + *h.Stale + "\""
|
ret += ", stale=\"" + *h.Stale + "\""
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.Method == AuthDigestMD5 {
|
if h.Algorithm != nil {
|
||||||
|
if *h.Algorithm == AuthAlgorithmMD5 {
|
||||||
ret += ", algorithm=\"MD5\""
|
ret += ", algorithm=\"MD5\""
|
||||||
} else {
|
} else {
|
||||||
ret += ", algorithm=\"SHA-256\""
|
ret += ", algorithm=\"SHA-256\""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return base.HeaderValue{ret}
|
return base.HeaderValue{ret}
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ var casesAuthenticate = []struct {
|
|||||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthBasic,
|
Method: AuthMethodBasic,
|
||||||
Realm: "4419b63f5e51",
|
Realm: "4419b63f5e51",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -31,9 +31,9 @@ var casesAuthenticate = []struct {
|
|||||||
"digest 1",
|
"digest 1",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||||
`stale="FALSE", algorithm="MD5"`},
|
`stale="FALSE"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Realm: "4419b63f5e51",
|
Realm: "4419b63f5e51",
|
||||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||||
Stale: stringPtr("FALSE"),
|
Stale: stringPtr("FALSE"),
|
||||||
@@ -43,9 +43,9 @@ var casesAuthenticate = []struct {
|
|||||||
"digest 2",
|
"digest 2",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||||
`stale="FALSE", algorithm="MD5"`},
|
`stale="FALSE"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Realm: "4419b63f5e51",
|
Realm: "4419b63f5e51",
|
||||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||||
Stale: stringPtr("FALSE"),
|
Stale: stringPtr("FALSE"),
|
||||||
@@ -55,9 +55,9 @@ var casesAuthenticate = []struct {
|
|||||||
"digest 3",
|
"digest 3",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", ` +
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", ` +
|
||||||
`stale="FALSE", algorithm="MD5"`},
|
`stale="FALSE"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Realm: "4419b63f5e51",
|
Realm: "4419b63f5e51",
|
||||||
Nonce: "133767111917411116111311118211673010032",
|
Nonce: "133767111917411116111311118211673010032",
|
||||||
Stale: stringPtr("FALSE"),
|
Stale: stringPtr("FALSE"),
|
||||||
@@ -66,17 +66,31 @@ var casesAuthenticate = []struct {
|
|||||||
{
|
{
|
||||||
"digest after failed auth",
|
"digest after failed auth",
|
||||||
base.HeaderValue{`Digest realm="Please log in with a valid username",` +
|
base.HeaderValue{`Digest realm="Please log in with a valid username",` +
|
||||||
`nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
|
`nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE`},
|
||||||
base.HeaderValue{`Digest realm="Please log in with a valid username", ` +
|
base.HeaderValue{`Digest realm="Please log in with a valid username", ` +
|
||||||
`nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
|
`nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Realm: "Please log in with a valid username",
|
Realm: "Please log in with a valid username",
|
||||||
Nonce: "752a62306daf32b401a41004555c7663",
|
Nonce: "752a62306daf32b401a41004555c7663",
|
||||||
Opaque: stringPtr(""),
|
Opaque: stringPtr(""),
|
||||||
Stale: stringPtr("FALSE"),
|
Stale: stringPtr("FALSE"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"digest md5 explicit",
|
||||||
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||||
|
`stale="FALSE", algorithm="MD5"`},
|
||||||
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||||
|
`stale="FALSE", algorithm="MD5"`},
|
||||||
|
Authenticate{
|
||||||
|
Method: AuthMethodDigest,
|
||||||
|
Realm: "4419b63f5e51",
|
||||||
|
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||||
|
Stale: stringPtr("FALSE"),
|
||||||
|
Algorithm: algorithmPtr(AuthAlgorithmMD5),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"digest sha256",
|
"digest sha256",
|
||||||
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
||||||
@@ -84,10 +98,11 @@ var casesAuthenticate = []struct {
|
|||||||
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
||||||
`nonce="fcc86deace979a488b2bfb89f4d0812c", stale="FALSE", algorithm="SHA-256"`},
|
`nonce="fcc86deace979a488b2bfb89f4d0812c", stale="FALSE", algorithm="SHA-256"`},
|
||||||
Authenticate{
|
Authenticate{
|
||||||
Method: AuthDigestSHA256,
|
Method: AuthMethodDigest,
|
||||||
Realm: "IP Camera(AB705)",
|
Realm: "IP Camera(AB705)",
|
||||||
Nonce: "fcc86deace979a488b2bfb89f4d0812c",
|
Nonce: "fcc86deace979a488b2bfb89f4d0812c",
|
||||||
Stale: stringPtr("FALSE"),
|
Stale: stringPtr("FALSE"),
|
||||||
|
Algorithm: algorithmPtr(AuthAlgorithmSHA256),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,9 @@ type Authorization struct {
|
|||||||
|
|
||||||
// opaque
|
// opaque
|
||||||
Opaque *string
|
Opaque *string
|
||||||
|
|
||||||
|
// algorithm
|
||||||
|
Algorithm *AuthAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal decodes an Authorization header.
|
// Unmarshal decodes an Authorization header.
|
||||||
@@ -64,20 +67,18 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
|||||||
}
|
}
|
||||||
method, v0 := v0[:i], v0[i+1:]
|
method, v0 := v0[:i], v0[i+1:]
|
||||||
|
|
||||||
isDigest := false
|
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "Basic":
|
case "Basic":
|
||||||
h.Method = AuthBasic
|
h.Method = AuthMethodBasic
|
||||||
|
|
||||||
case "Digest":
|
case "Digest":
|
||||||
isDigest = true
|
h.Method = AuthMethodDigest
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid method (%s)", method)
|
return fmt.Errorf("invalid method (%s)", method)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDigest {
|
if h.Method == AuthMethodBasic {
|
||||||
tmp, err := base64.StdEncoding.DecodeString(v0)
|
tmp, err := base64.StdEncoding.DecodeString(v0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value")
|
return fmt.Errorf("invalid value")
|
||||||
@@ -100,7 +101,6 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
|||||||
nonceReceived := false
|
nonceReceived := false
|
||||||
uriReceived := false
|
uriReceived := false
|
||||||
responseReceived := false
|
responseReceived := false
|
||||||
var algorithm *string
|
|
||||||
|
|
||||||
for k, rv := range kvs {
|
for k, rv := range kvs {
|
||||||
v := rv
|
v := rv
|
||||||
@@ -130,18 +130,17 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
|||||||
h.Opaque = &v
|
h.Opaque = &v
|
||||||
|
|
||||||
case "algorithm":
|
case "algorithm":
|
||||||
algorithm = &v
|
a, err := parseAuthAlgorithm(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.Algorithm = &a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !realmReceived || !usernameReceived || !nonceReceived || !uriReceived || !responseReceived {
|
if !realmReceived || !usernameReceived || !nonceReceived || !uriReceived || !responseReceived {
|
||||||
return fmt.Errorf("one or more digest fields are missing")
|
return fmt.Errorf("one or more digest fields are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Method, err = algorithmToMethod(algorithm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -149,7 +148,7 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
|||||||
|
|
||||||
// Marshal encodes an Authorization header.
|
// Marshal encodes an Authorization header.
|
||||||
func (h Authorization) Marshal() base.HeaderValue {
|
func (h Authorization) Marshal() base.HeaderValue {
|
||||||
if h.Method == AuthBasic {
|
if h.Method == AuthMethodBasic {
|
||||||
return base.HeaderValue{"Basic " +
|
return base.HeaderValue{"Basic " +
|
||||||
base64.StdEncoding.EncodeToString([]byte(h.BasicUser+":"+h.BasicPass))}
|
base64.StdEncoding.EncodeToString([]byte(h.BasicUser+":"+h.BasicPass))}
|
||||||
}
|
}
|
||||||
@@ -162,11 +161,13 @@ func (h Authorization) Marshal() base.HeaderValue {
|
|||||||
ret += ", opaque=\"" + *h.Opaque + "\""
|
ret += ", opaque=\"" + *h.Opaque + "\""
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.Method == AuthDigestMD5 {
|
if h.Algorithm != nil {
|
||||||
|
if *h.Algorithm == AuthAlgorithmMD5 {
|
||||||
ret += ", algorithm=\"MD5\""
|
ret += ", algorithm=\"MD5\""
|
||||||
} else {
|
} else {
|
||||||
ret += ", algorithm=\"SHA-256\""
|
ret += ", algorithm=\"SHA-256\""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return base.HeaderValue{ret}
|
return base.HeaderValue{ret}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,10 @@ import (
|
|||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func algorithmPtr(v AuthAlgorithm) *AuthAlgorithm {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
var casesAuthorization = []struct {
|
var casesAuthorization = []struct {
|
||||||
name string
|
name string
|
||||||
vin base.HeaderValue
|
vin base.HeaderValue
|
||||||
@@ -19,7 +23,7 @@ var casesAuthorization = []struct {
|
|||||||
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
||||||
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
||||||
Authorization{
|
Authorization{
|
||||||
Method: AuthBasic,
|
Method: AuthMethodBasic,
|
||||||
BasicUser: "myuser",
|
BasicUser: "myuser",
|
||||||
BasicPass: "mypass",
|
BasicPass: "mypass",
|
||||||
},
|
},
|
||||||
@@ -31,9 +35,9 @@ var casesAuthorization = []struct {
|
|||||||
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||||
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||||
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", ` +
|
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", ` +
|
||||||
`response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41", algorithm="MD5"`},
|
`response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||||
Authorization{
|
Authorization{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Username: "Mufasa",
|
Username: "Mufasa",
|
||||||
Realm: "testrealm@host.com",
|
Realm: "testrealm@host.com",
|
||||||
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||||
@@ -49,9 +53,9 @@ var casesAuthorization = []struct {
|
|||||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1", algorithm="MD5"`},
|
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||||
Authorization{
|
Authorization{
|
||||||
Method: AuthDigestMD5,
|
Method: AuthMethodDigest,
|
||||||
Username: "",
|
Username: "",
|
||||||
Realm: "IPCAM",
|
Realm: "IPCAM",
|
||||||
Nonce: "5d17cd12b9fa8a85ac5ceef0926ea5a6",
|
Nonce: "5d17cd12b9fa8a85ac5ceef0926ea5a6",
|
||||||
@@ -59,6 +63,26 @@ var casesAuthorization = []struct {
|
|||||||
Response: "c072ae90eb4a27f4cdcb90d62266b2a1",
|
Response: "c072ae90eb4a27f4cdcb90d62266b2a1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"digest explicit md5",
|
||||||
|
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||||
|
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ` +
|
||||||
|
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", ` +
|
||||||
|
`opaque="5ccc069c403ebaf9f0171e9517f40e41", algorithm="MD5"`},
|
||||||
|
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||||
|
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", ` +
|
||||||
|
`response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41", algorithm="MD5"`},
|
||||||
|
Authorization{
|
||||||
|
Method: AuthMethodDigest,
|
||||||
|
Username: "Mufasa",
|
||||||
|
Realm: "testrealm@host.com",
|
||||||
|
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||||
|
URI: "/dir/index.html",
|
||||||
|
Response: "e966c932a9242554e42c8ee200cec7f6",
|
||||||
|
Opaque: stringPtr("5ccc069c403ebaf9f0171e9517f40e41"),
|
||||||
|
Algorithm: algorithmPtr(AuthAlgorithmMD5),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"digest sha256",
|
"digest sha256",
|
||||||
base.HeaderValue{`Digest username="admin", realm="IP Camera(AB705)", ` +
|
base.HeaderValue{`Digest username="admin", realm="IP Camera(AB705)", ` +
|
||||||
@@ -68,12 +92,13 @@ var casesAuthorization = []struct {
|
|||||||
`nonce="1ad195c2b2ca5a03784e53f88e16f579", uri="rtsp://192.168.80.76/", ` +
|
`nonce="1ad195c2b2ca5a03784e53f88e16f579", uri="rtsp://192.168.80.76/", ` +
|
||||||
`response="9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913", algorithm="SHA-256"`},
|
`response="9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913", algorithm="SHA-256"`},
|
||||||
Authorization{
|
Authorization{
|
||||||
Method: AuthDigestSHA256,
|
Method: AuthMethodDigest,
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Realm: "IP Camera(AB705)",
|
Realm: "IP Camera(AB705)",
|
||||||
Nonce: "1ad195c2b2ca5a03784e53f88e16f579",
|
Nonce: "1ad195c2b2ca5a03784e53f88e16f579",
|
||||||
URI: "rtsp://192.168.80.76/",
|
URI: "rtsp://192.168.80.76/",
|
||||||
Response: "9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913",
|
Response: "9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913",
|
||||||
|
Algorithm: algorithmPtr(AuthAlgorithmSHA256),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user