mirror of
https://github.com/aler9/gortsplib
synced 2025-10-16 12:10:48 +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:
@@ -11,34 +11,31 @@ import (
|
||||
// AuthMethod is an authentication method.
|
||||
type AuthMethod int
|
||||
|
||||
// authentication methods.
|
||||
const (
|
||||
// AuthBasic is the Basic authentication method
|
||||
AuthBasic AuthMethod = iota
|
||||
|
||||
// AuthDigestMD5 is the Digest authentication method with the MD5 hash
|
||||
AuthDigestMD5
|
||||
|
||||
// AuthDigestSHA256 is the Digest authentication method with the SHA-256 hash
|
||||
AuthDigestSHA256
|
||||
AuthMethodBasic AuthMethod = iota
|
||||
AuthMethodDigest
|
||||
)
|
||||
|
||||
// AuthAlgorithm is a digest algorithm.
|
||||
type AuthAlgorithm int
|
||||
|
||||
// digest algorithms.
|
||||
const (
|
||||
// AuthDigest is an alias for AuthDigestMD5
|
||||
//
|
||||
// Deprecated: replaced by AuthDigestMD5
|
||||
AuthDigest = AuthDigestMD5
|
||||
AuthAlgorithmMD5 AuthAlgorithm = iota
|
||||
AuthAlgorithmSHA256
|
||||
)
|
||||
|
||||
func algorithmToMethod(v *string) (AuthMethod, error) {
|
||||
func parseAuthAlgorithm(v string) (AuthAlgorithm, error) {
|
||||
switch {
|
||||
case v == nil, strings.ToLower(*v) == "md5":
|
||||
return AuthDigestMD5, nil
|
||||
case strings.ToLower(v) == "md5":
|
||||
return AuthAlgorithmMD5, nil
|
||||
|
||||
case strings.ToLower(*v) == "sha-256":
|
||||
return AuthDigestSHA256, nil
|
||||
case strings.ToLower(v) == "sha-256":
|
||||
return AuthAlgorithmSHA256, nil
|
||||
|
||||
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 *string
|
||||
|
||||
// algorithm
|
||||
Algorithm *AuthAlgorithm
|
||||
}
|
||||
|
||||
// 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:]
|
||||
|
||||
isDigest := false
|
||||
|
||||
switch method {
|
||||
case "Basic":
|
||||
h.Method = AuthBasic
|
||||
h.Method = AuthMethodBasic
|
||||
|
||||
case "Digest":
|
||||
isDigest = true
|
||||
h.Method = AuthMethodDigest
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid method (%s)", method)
|
||||
}
|
||||
|
||||
if !isDigest {
|
||||
if h.Method == AuthMethodBasic {
|
||||
kvs, err := keyValParse(v0, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,7 +121,6 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
|
||||
realmReceived := false
|
||||
nonceReceived := false
|
||||
var algorithm *string
|
||||
|
||||
for k, rv := range kvs {
|
||||
v := rv
|
||||
@@ -144,18 +141,17 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
h.Stale = &v
|
||||
|
||||
case "algorithm":
|
||||
algorithm = &v
|
||||
a, err := parseAuthAlgorithm(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Algorithm = &a
|
||||
}
|
||||
}
|
||||
|
||||
if !realmReceived || !nonceReceived {
|
||||
return fmt.Errorf("one or more digest fields are missing")
|
||||
}
|
||||
|
||||
h.Method, err = algorithmToMethod(algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -163,7 +159,7 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
|
||||
// Marshal encodes a WWW-Authenticate header.
|
||||
func (h Authenticate) Marshal() base.HeaderValue {
|
||||
if h.Method == AuthBasic {
|
||||
if h.Method == AuthMethodBasic {
|
||||
return base.HeaderValue{"Basic " +
|
||||
"realm=\"" + h.Realm + "\""}
|
||||
}
|
||||
@@ -178,10 +174,12 @@ func (h Authenticate) Marshal() base.HeaderValue {
|
||||
ret += ", stale=\"" + *h.Stale + "\""
|
||||
}
|
||||
|
||||
if h.Method == AuthDigestMD5 {
|
||||
ret += ", algorithm=\"MD5\""
|
||||
} else {
|
||||
ret += ", algorithm=\"SHA-256\""
|
||||
if h.Algorithm != nil {
|
||||
if *h.Algorithm == AuthAlgorithmMD5 {
|
||||
ret += ", algorithm=\"MD5\""
|
||||
} else {
|
||||
ret += ", algorithm=\"SHA-256\""
|
||||
}
|
||||
}
|
||||
|
||||
return base.HeaderValue{ret}
|
||||
|
@@ -23,7 +23,7 @@ var casesAuthenticate = []struct {
|
||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||
Authenticate{
|
||||
Method: AuthBasic,
|
||||
Method: AuthMethodBasic,
|
||||
Realm: "4419b63f5e51",
|
||||
},
|
||||
},
|
||||
@@ -31,9 +31,9 @@ var casesAuthenticate = []struct {
|
||||
"digest 1",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||
`stale="FALSE", algorithm="MD5"`},
|
||||
`stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||
Stale: stringPtr("FALSE"),
|
||||
@@ -43,9 +43,9 @@ var casesAuthenticate = []struct {
|
||||
"digest 2",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", ` +
|
||||
`stale="FALSE", algorithm="MD5"`},
|
||||
`stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||
Stale: stringPtr("FALSE"),
|
||||
@@ -55,9 +55,9 @@ var casesAuthenticate = []struct {
|
||||
"digest 3",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", ` +
|
||||
`stale="FALSE", algorithm="MD5"`},
|
||||
`stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "133767111917411116111311118211673010032",
|
||||
Stale: stringPtr("FALSE"),
|
||||
@@ -66,17 +66,31 @@ var casesAuthenticate = []struct {
|
||||
{
|
||||
"digest after failed auth",
|
||||
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", ` +
|
||||
`nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
|
||||
`nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Realm: "Please log in with a valid username",
|
||||
Nonce: "752a62306daf32b401a41004555c7663",
|
||||
Opaque: stringPtr(""),
|
||||
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",
|
||||
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
||||
@@ -84,10 +98,11 @@ var casesAuthenticate = []struct {
|
||||
base.HeaderValue{`Digest realm="IP Camera(AB705)", ` +
|
||||
`nonce="fcc86deace979a488b2bfb89f4d0812c", stale="FALSE", algorithm="SHA-256"`},
|
||||
Authenticate{
|
||||
Method: AuthDigestSHA256,
|
||||
Realm: "IP Camera(AB705)",
|
||||
Nonce: "fcc86deace979a488b2bfb89f4d0812c",
|
||||
Stale: stringPtr("FALSE"),
|
||||
Method: AuthMethodDigest,
|
||||
Realm: "IP Camera(AB705)",
|
||||
Nonce: "fcc86deace979a488b2bfb89f4d0812c",
|
||||
Stale: stringPtr("FALSE"),
|
||||
Algorithm: algorithmPtr(AuthAlgorithmSHA256),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -44,6 +44,9 @@ type Authorization struct {
|
||||
|
||||
// opaque
|
||||
Opaque *string
|
||||
|
||||
// algorithm
|
||||
Algorithm *AuthAlgorithm
|
||||
}
|
||||
|
||||
// Unmarshal decodes an Authorization header.
|
||||
@@ -64,20 +67,18 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
}
|
||||
method, v0 := v0[:i], v0[i+1:]
|
||||
|
||||
isDigest := false
|
||||
|
||||
switch method {
|
||||
case "Basic":
|
||||
h.Method = AuthBasic
|
||||
h.Method = AuthMethodBasic
|
||||
|
||||
case "Digest":
|
||||
isDigest = true
|
||||
h.Method = AuthMethodDigest
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid method (%s)", method)
|
||||
}
|
||||
|
||||
if !isDigest {
|
||||
if h.Method == AuthMethodBasic {
|
||||
tmp, err := base64.StdEncoding.DecodeString(v0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value")
|
||||
@@ -100,7 +101,6 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
nonceReceived := false
|
||||
uriReceived := false
|
||||
responseReceived := false
|
||||
var algorithm *string
|
||||
|
||||
for k, rv := range kvs {
|
||||
v := rv
|
||||
@@ -130,18 +130,17 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
h.Opaque = &v
|
||||
|
||||
case "algorithm":
|
||||
algorithm = &v
|
||||
a, err := parseAuthAlgorithm(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Algorithm = &a
|
||||
}
|
||||
}
|
||||
|
||||
if !realmReceived || !usernameReceived || !nonceReceived || !uriReceived || !responseReceived {
|
||||
return fmt.Errorf("one or more digest fields are missing")
|
||||
}
|
||||
|
||||
h.Method, err = algorithmToMethod(algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -149,7 +148,7 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
|
||||
// Marshal encodes an Authorization header.
|
||||
func (h Authorization) Marshal() base.HeaderValue {
|
||||
if h.Method == AuthBasic {
|
||||
if h.Method == AuthMethodBasic {
|
||||
return base.HeaderValue{"Basic " +
|
||||
base64.StdEncoding.EncodeToString([]byte(h.BasicUser+":"+h.BasicPass))}
|
||||
}
|
||||
@@ -162,10 +161,12 @@ func (h Authorization) Marshal() base.HeaderValue {
|
||||
ret += ", opaque=\"" + *h.Opaque + "\""
|
||||
}
|
||||
|
||||
if h.Method == AuthDigestMD5 {
|
||||
ret += ", algorithm=\"MD5\""
|
||||
} else {
|
||||
ret += ", algorithm=\"SHA-256\""
|
||||
if h.Algorithm != nil {
|
||||
if *h.Algorithm == AuthAlgorithmMD5 {
|
||||
ret += ", algorithm=\"MD5\""
|
||||
} else {
|
||||
ret += ", algorithm=\"SHA-256\""
|
||||
}
|
||||
}
|
||||
|
||||
return base.HeaderValue{ret}
|
||||
|
@@ -8,6 +8,10 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||
)
|
||||
|
||||
func algorithmPtr(v AuthAlgorithm) *AuthAlgorithm {
|
||||
return &v
|
||||
}
|
||||
|
||||
var casesAuthorization = []struct {
|
||||
name string
|
||||
vin base.HeaderValue
|
||||
@@ -19,7 +23,7 @@ var casesAuthorization = []struct {
|
||||
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
||||
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
||||
Authorization{
|
||||
Method: AuthBasic,
|
||||
Method: AuthMethodBasic,
|
||||
BasicUser: "myuser",
|
||||
BasicPass: "mypass",
|
||||
},
|
||||
@@ -31,9 +35,9 @@ var casesAuthorization = []struct {
|
||||
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", ` +
|
||||
`response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41", algorithm="MD5"`},
|
||||
`response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||
Authorization{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Username: "Mufasa",
|
||||
Realm: "testrealm@host.com",
|
||||
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||
@@ -49,9 +53,9 @@ var casesAuthorization = []struct {
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1", algorithm="MD5"`},
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
Authorization{
|
||||
Method: AuthDigestMD5,
|
||||
Method: AuthMethodDigest,
|
||||
Username: "",
|
||||
Realm: "IPCAM",
|
||||
Nonce: "5d17cd12b9fa8a85ac5ceef0926ea5a6",
|
||||
@@ -59,6 +63,26 @@ var casesAuthorization = []struct {
|
||||
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",
|
||||
base.HeaderValue{`Digest username="admin", realm="IP Camera(AB705)", ` +
|
||||
@@ -68,12 +92,13 @@ var casesAuthorization = []struct {
|
||||
`nonce="1ad195c2b2ca5a03784e53f88e16f579", uri="rtsp://192.168.80.76/", ` +
|
||||
`response="9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913", algorithm="SHA-256"`},
|
||||
Authorization{
|
||||
Method: AuthDigestSHA256,
|
||||
Username: "admin",
|
||||
Realm: "IP Camera(AB705)",
|
||||
Nonce: "1ad195c2b2ca5a03784e53f88e16f579",
|
||||
URI: "rtsp://192.168.80.76/",
|
||||
Response: "9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913",
|
||||
Method: AuthMethodDigest,
|
||||
Username: "admin",
|
||||
Realm: "IP Camera(AB705)",
|
||||
Nonce: "1ad195c2b2ca5a03784e53f88e16f579",
|
||||
URI: "rtsp://192.168.80.76/",
|
||||
Response: "9e2324f104f3ce507d17e44a78fc1293001fe84805bde65d2aaa9be97a5a8913",
|
||||
Algorithm: algorithmPtr(AuthAlgorithmSHA256),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user