rtsp: fix authentication regression (#4557)
since #4267 it was impossible to perform authentication when protocol is RTSP and credentials are hashed.
This commit is contained in:
@@ -42,6 +42,15 @@ func credentialsProvided(req *base.Request) bool {
|
||||
return err == nil && auth.Username != ""
|
||||
}
|
||||
|
||||
func contains(list []rtspauth.VerifyMethod, item rtspauth.VerifyMethod) bool {
|
||||
for _, i := range list {
|
||||
if i == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type connParent interface {
|
||||
logger.Writer
|
||||
findSessionByRSessionUnsafe(rsession *gortsplib.ServerSession) *session
|
||||
@@ -138,16 +147,23 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
||||
}
|
||||
ctx.Path = ctx.Path[1:]
|
||||
|
||||
req := defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
IP: c.ip(),
|
||||
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
|
||||
// CustomVerifyFunc prevents hashed credentials from working.
|
||||
// Use it only when strictly needed.
|
||||
var customVerifyFunc func(expectedUser, expectedPass string) bool
|
||||
if contains(c.authMethods, rtspauth.VerifyMethodDigestMD5) {
|
||||
customVerifyFunc = func(expectedUser, expectedPass string) bool {
|
||||
return c.rconn.VerifyCredentials(ctx.Request, expectedUser, expectedPass)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
req := defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
IP: c.ip(),
|
||||
CustomVerifyFunc: customVerifyFunc,
|
||||
}
|
||||
|
||||
res := c.pathManager.Describe(defs.PathDescribeReq{
|
||||
|
||||
@@ -64,192 +64,249 @@ func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
|
||||
}
|
||||
|
||||
func TestServerPublish(t *testing.T) {
|
||||
path := &dummyPath{
|
||||
streamCreated: make(chan struct{}),
|
||||
}
|
||||
|
||||
pathManager := &test.PathManager{
|
||||
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
|
||||
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
|
||||
return nil, auth.Error{Message: "", AskCredentials: true}
|
||||
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
path := &dummyPath{
|
||||
streamCreated: make(chan struct{}),
|
||||
}
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
return path, nil
|
||||
},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Address: "127.0.0.1:8557",
|
||||
AuthMethods: []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic},
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
WriteTimeout: conf.Duration(10 * time.Second),
|
||||
WriteQueueSize: 512,
|
||||
Transports: conf.RTSPTransports{gortsplib.TransportTCP: {}},
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
n := 0
|
||||
|
||||
source := gortsplib.Client{}
|
||||
pathManager := &test.PathManager{
|
||||
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
|
||||
if ca == "basic" {
|
||||
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
|
||||
return nil, auth.Error{Message: "", AskCredentials: true}
|
||||
}
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
} else {
|
||||
ok := req.AccessRequest.CustomVerifyFunc("myuser", "mypass")
|
||||
if n == 0 {
|
||||
require.False(t, ok)
|
||||
n++
|
||||
return nil, auth.Error{Message: "", AskCredentials: true}
|
||||
}
|
||||
require.True(t, ok)
|
||||
}
|
||||
return path, nil
|
||||
},
|
||||
}
|
||||
|
||||
media0 := test.UniqueMediaH264()
|
||||
var authMethods []rtspauth.VerifyMethod
|
||||
switch ca {
|
||||
case "basic":
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic}
|
||||
case "digest":
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodDigestMD5}
|
||||
default:
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic, rtspauth.VerifyMethodDigestMD5}
|
||||
}
|
||||
|
||||
err = source.StartRecording(
|
||||
"rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value",
|
||||
&description.Session{Medias: []*description.Media{media0}})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
s := &Server{
|
||||
Address: "127.0.0.1:8557",
|
||||
AuthMethods: authMethods,
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
WriteTimeout: conf.Duration(10 * time.Second),
|
||||
WriteQueueSize: 512,
|
||||
Transports: conf.RTSPTransports{gortsplib.TransportTCP: {}},
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
<-path.streamCreated
|
||||
source := gortsplib.Client{}
|
||||
|
||||
reader := test.NilLogger
|
||||
media0 := test.UniqueMediaH264()
|
||||
|
||||
recv := make(chan struct{})
|
||||
err = source.StartRecording(
|
||||
"rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value",
|
||||
&description.Session{Medias: []*description.Media{media0}})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
path.stream.AddReader(
|
||||
reader,
|
||||
path.stream.Desc.Medias[0],
|
||||
path.stream.Desc.Medias[0].Formats[0],
|
||||
func(u unit.Unit) error {
|
||||
require.Equal(t, [][]byte{
|
||||
test.FormatH264.SPS,
|
||||
test.FormatH264.PPS,
|
||||
{5, 2, 3, 4},
|
||||
}, u.(*unit.H264).AU)
|
||||
close(recv)
|
||||
return nil
|
||||
<-path.streamCreated
|
||||
|
||||
reader := test.NilLogger
|
||||
|
||||
recv := make(chan struct{})
|
||||
|
||||
path.stream.AddReader(
|
||||
reader,
|
||||
path.stream.Desc.Medias[0],
|
||||
path.stream.Desc.Medias[0].Formats[0],
|
||||
func(u unit.Unit) error {
|
||||
require.Equal(t, [][]byte{
|
||||
test.FormatH264.SPS,
|
||||
test.FormatH264.PPS,
|
||||
{5, 2, 3, 4},
|
||||
}, u.(*unit.H264).AU)
|
||||
close(recv)
|
||||
return nil
|
||||
})
|
||||
|
||||
path.stream.StartReader(reader)
|
||||
defer path.stream.RemoveReader(reader)
|
||||
|
||||
err = source.WritePacketRTP(media0, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{5, 2, 3, 4},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-recv
|
||||
})
|
||||
|
||||
path.stream.StartReader(reader)
|
||||
defer path.stream.RemoveReader(reader)
|
||||
|
||||
err = source.WritePacketRTP(media0, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
},
|
||||
Payload: []byte{5, 2, 3, 4},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-recv
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerRead(t *testing.T) {
|
||||
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||
|
||||
strm := &stream.Stream{
|
||||
WriteQueueSize: 512,
|
||||
UDPMaxPayloadSize: 1472,
|
||||
Desc: desc,
|
||||
GenerateRTPPackets: true,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
path := &dummyPath{stream: strm}
|
||||
|
||||
pathManager := &test.PathManager{
|
||||
DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
|
||||
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
|
||||
return defs.PathDescribeRes{Err: auth.Error{Message: "", AskCredentials: true}}
|
||||
strm := &stream.Stream{
|
||||
WriteQueueSize: 512,
|
||||
UDPMaxPayloadSize: 1472,
|
||||
Desc: desc,
|
||||
GenerateRTPPackets: true,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
return defs.PathDescribeRes{
|
||||
Path: path,
|
||||
Stream: path.stream,
|
||||
Err: nil,
|
||||
path := &dummyPath{stream: strm}
|
||||
n := 0
|
||||
|
||||
pathManager := &test.PathManager{
|
||||
DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
|
||||
if ca == "basic" {
|
||||
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
|
||||
return defs.PathDescribeRes{Err: auth.Error{Message: "", AskCredentials: true}}
|
||||
}
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
} else {
|
||||
ok := req.AccessRequest.CustomVerifyFunc("myuser", "mypass")
|
||||
if n == 0 {
|
||||
require.False(t, ok)
|
||||
n++
|
||||
return defs.PathDescribeRes{Err: auth.Error{Message: "", AskCredentials: true}}
|
||||
}
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
return defs.PathDescribeRes{
|
||||
Path: path,
|
||||
Stream: path.stream,
|
||||
Err: nil,
|
||||
}
|
||||
},
|
||||
AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
|
||||
if ca == "basic" {
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
} else {
|
||||
ok := req.AccessRequest.CustomVerifyFunc("myuser", "mypass")
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
return path, path.stream, nil
|
||||
},
|
||||
}
|
||||
},
|
||||
AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
|
||||
require.Equal(t, "teststream", req.AccessRequest.Name)
|
||||
require.Equal(t, "param=value", req.AccessRequest.Query)
|
||||
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
|
||||
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
|
||||
return path, path.stream, nil
|
||||
},
|
||||
|
||||
var authMethods []rtspauth.VerifyMethod
|
||||
switch ca {
|
||||
case "basic":
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic}
|
||||
case "digest":
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodDigestMD5}
|
||||
default:
|
||||
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic, rtspauth.VerifyMethodDigestMD5}
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Address: "127.0.0.1:8557",
|
||||
AuthMethods: authMethods,
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
WriteTimeout: conf.Duration(10 * time.Second),
|
||||
WriteQueueSize: 512,
|
||||
Transports: conf.RTSPTransports{gortsplib.TransportTCP: {}},
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err = s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
reader := gortsplib.Client{}
|
||||
|
||||
u, err := base.ParseURL("rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.Start(u.Scheme, u.Host)
|
||||
require.NoError(t, err)
|
||||
defer reader.Close()
|
||||
|
||||
desc2, _, err := reader.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.SetupAll(desc2.BaseURL, desc2.Medias)
|
||||
require.NoError(t, err)
|
||||
|
||||
recv := make(chan struct{})
|
||||
|
||||
reader.OnPacketRTPAny(func(_ *description.Media, _ format.Format, p *rtp.Packet) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: p.SequenceNumber,
|
||||
Timestamp: p.Timestamp,
|
||||
SSRC: p.SSRC,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{
|
||||
0x18, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
|
||||
0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00,
|
||||
0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0,
|
||||
0x3c, 0x60, 0xc9, 0x20, 0x00, 0x04, 0x08, 0x06,
|
||||
0x07, 0x08, 0x00, 0x04, 0x05, 0x02, 0x03, 0x04,
|
||||
},
|
||||
}, p)
|
||||
close(recv)
|
||||
})
|
||||
|
||||
_, err = reader.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
|
||||
Base: unit.Base{
|
||||
NTP: time.Time{},
|
||||
},
|
||||
AU: [][]byte{
|
||||
{5, 2, 3, 4}, // IDR
|
||||
},
|
||||
})
|
||||
|
||||
<-recv
|
||||
})
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Address: "127.0.0.1:8557",
|
||||
AuthMethods: []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic},
|
||||
ReadTimeout: conf.Duration(10 * time.Second),
|
||||
WriteTimeout: conf.Duration(10 * time.Second),
|
||||
WriteQueueSize: 512,
|
||||
Transports: conf.RTSPTransports{gortsplib.TransportTCP: {}},
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err = s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
reader := gortsplib.Client{}
|
||||
|
||||
u, err := base.ParseURL("rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.Start(u.Scheme, u.Host)
|
||||
require.NoError(t, err)
|
||||
defer reader.Close()
|
||||
|
||||
desc2, _, err := reader.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.SetupAll(desc2.BaseURL, desc2.Medias)
|
||||
require.NoError(t, err)
|
||||
|
||||
recv := make(chan struct{})
|
||||
|
||||
reader.OnPacketRTPAny(func(_ *description.Media, _ format.Format, p *rtp.Packet) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: p.SequenceNumber,
|
||||
Timestamp: p.Timestamp,
|
||||
SSRC: p.SSRC,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{
|
||||
0x18, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
|
||||
0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00,
|
||||
0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0,
|
||||
0x3c, 0x60, 0xc9, 0x20, 0x00, 0x04, 0x08, 0x06,
|
||||
0x07, 0x08, 0x00, 0x04, 0x05, 0x02, 0x03, 0x04,
|
||||
},
|
||||
}, p)
|
||||
close(recv)
|
||||
})
|
||||
|
||||
_, err = reader.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
|
||||
Base: unit.Base{
|
||||
NTP: time.Time{},
|
||||
},
|
||||
AU: [][]byte{
|
||||
{5, 2, 3, 4}, // IDR
|
||||
},
|
||||
})
|
||||
|
||||
<-recv
|
||||
}
|
||||
|
||||
func TestServerRedirect(t *testing.T) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4"
|
||||
rtspauth "github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -144,17 +145,24 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
|
||||
}
|
||||
ctx.Path = ctx.Path[1:]
|
||||
|
||||
req := defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
Publish: true,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
IP: c.ip(),
|
||||
CustomVerifyFunc: func(expectedUser, expectedPass string) bool {
|
||||
// CustomVerifyFunc prevents hashed credentials from working.
|
||||
// Use it only when strictly needed.
|
||||
var customVerifyFunc func(expectedUser, expectedPass string) bool
|
||||
if contains(c.authMethods, rtspauth.VerifyMethodDigestMD5) {
|
||||
customVerifyFunc = func(expectedUser, expectedPass string) bool {
|
||||
return c.rconn.VerifyCredentials(ctx.Request, expectedUser, expectedPass)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
req := defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
Publish: true,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
IP: c.ip(),
|
||||
CustomVerifyFunc: customVerifyFunc,
|
||||
}
|
||||
|
||||
path, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
|
||||
|
||||
Reference in New Issue
Block a user