From bf3ce1147917c7f9850fc1fc104db6a4b4f06351 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Mon, 30 Jun 2025 16:03:05 +0200 Subject: [PATCH] client: prevent downgrading from RTSPS to RTSP during redirect (#816) --- client.go | 4 +++ client_play_test.go | 83 ++++++++++++++++++++++++++++++++++++------- client_record_test.go | 26 +++++++++----- 3 files changed, 93 insertions(+), 20 deletions(-) diff --git a/client.go b/client.go index 20ff8265..ca098b74 100644 --- a/client.go +++ b/client.go @@ -1256,6 +1256,10 @@ func (c *Client) doDescribe(u *base.URL) (*description.Session, *base.Response, return nil, nil, err } + if c.connURL.Scheme == "rtsps" && ru.Scheme != "rtsps" { + return nil, nil, fmt.Errorf("connection cannot be downgraded from RTSPS to RTSP") + } + if u.User != nil { ru.User = u.User } diff --git a/client_play_test.go b/client_play_test.go index 168131da..af5b3b5c 100644 --- a/client_play_test.go +++ b/client_play_test.go @@ -252,9 +252,8 @@ func TestClientPlay(t *testing.T) { packetRecv := make(chan struct{}) listenIP := multicastCapableIP(t) - l, err := net.Listen("tcp", listenIP+":8554") - require.NoError(t, err) - defer l.Close() + var l net.Listener + var err error var scheme string if transport == "tls" { @@ -264,9 +263,15 @@ func TestClientPlay(t *testing.T) { cert, err = tls.X509KeyPair(serverCert, serverKey) require.NoError(t, err) - l = tls.NewListener(l, &tls.Config{Certificates: []tls.Certificate{cert}}) + l, err = tls.Listen("tcp", listenIP+":8554", &tls.Config{Certificates: []tls.Certificate{cert}}) + require.NoError(t, err) + defer l.Close() } else { scheme = "rtsp" + + l, err = net.Listen("tcp", listenIP+":8554") + require.NoError(t, err) + defer l.Close() } serverDone := make(chan struct{}) @@ -1650,12 +1655,11 @@ func TestClientPlayDifferentInterleavedIDs(t *testing.T) { } func TestClientPlayRedirect(t *testing.T) { - for _, withCredentials := range []bool{false, true} { - runName := "WithoutCredentials" - if withCredentials { - runName = "WithCredentials" - } - t.Run(runName, func(t *testing.T) { + for _, ca := range []string{ + "without credentials", + "with credentials", + } { + t.Run(ca, func(t *testing.T) { l, err := net.Listen("tcp", "localhost:8554") require.NoError(t, err) defer l.Close() @@ -1728,7 +1732,7 @@ func TestClientPlayRedirect(t *testing.T) { require.NoError(t, err2) require.Equal(t, base.Describe, req.Method) - if withCredentials { + if ca == "with credentials" { if _, exists := req.Header["Authorization"]; !exists { authRealm := "example@localhost" authNonce := "exampleNonce" @@ -1823,7 +1827,7 @@ func TestClientPlayRedirect(t *testing.T) { c := Client{} ru := "rtsp://localhost:8554/path1" - if withCredentials { + if ca == "with credentials" { ru = "rtsp://testusr:testpwd@localhost:8554/path1" } err = readAll(&c, ru, @@ -1838,6 +1842,61 @@ func TestClientPlayRedirect(t *testing.T) { } } +func TestClientPlayRedirectPreventDecrypt(t *testing.T) { + cert, err := tls.X509KeyPair(serverCert, serverKey) + require.NoError(t, err) + + l, err := tls.Listen("tcp", "localhost:8554", &tls.Config{Certificates: []tls.Certificate{cert}}) + require.NoError(t, err) + defer l.Close() + + serverDone := make(chan struct{}) + defer func() { <-serverDone }() + + go func() { + defer close(serverDone) + + var nconn net.Conn + nconn, err = l.Accept() + require.NoError(t, err) + defer nconn.Close() + conn := conn.NewConn(nconn) + + req, err2 := conn.ReadRequest() + require.NoError(t, err2) + require.Equal(t, base.Options, req.Method) + + err2 = conn.WriteResponse(&base.Response{ + StatusCode: base.StatusOK, + Header: base.Header{ + "Public": base.HeaderValue{strings.Join([]string{ + string(base.Describe), + string(base.Setup), + string(base.Play), + }, ", ")}, + }, + }) + require.NoError(t, err2) + + req, err2 = conn.ReadRequest() + require.NoError(t, err2) + require.Equal(t, base.Describe, req.Method) + + err2 = conn.WriteResponse(&base.Response{ + StatusCode: base.StatusMovedPermanently, + Header: base.Header{ + "Location": base.HeaderValue{"rtsp://localhost:8554/test"}, + }, + }) + require.NoError(t, err2) + }() + + c := Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}} + err = readAll(&c, "rtsps://localhost:8554/test", nil) + require.EqualError(t, err, "connection cannot be downgraded from RTSPS to RTSP") + defer c.Close() +} + func TestClientPlayPausePlay(t *testing.T) { writeFrames := func(inTH *headers.Transport, conn *conn.Conn) (chan struct{}, chan struct{}) { writerTerminate := make(chan struct{}) diff --git a/client_record_test.go b/client_record_test.go index b388051d..be3476b0 100644 --- a/client_record_test.go +++ b/client_record_test.go @@ -132,9 +132,8 @@ func TestClientRecord(t *testing.T) { "tls", } { t.Run(transport, func(t *testing.T) { - l, err := net.Listen("tcp", "localhost:8554") - require.NoError(t, err) - defer l.Close() + var l net.Listener + var err error var scheme string if transport == "tls" { @@ -144,9 +143,15 @@ func TestClientRecord(t *testing.T) { cert, err = tls.X509KeyPair(serverCert, serverKey) require.NoError(t, err) - l = tls.NewListener(l, &tls.Config{Certificates: []tls.Certificate{cert}}) + l, err = tls.Listen("tcp", "localhost:8554", &tls.Config{Certificates: []tls.Certificate{cert}}) + require.NoError(t, err) + defer l.Close() } else { scheme = "rtsp" + + l, err = net.Listen("tcp", "localhost:8554") + require.NoError(t, err) + defer l.Close() } serverDone := make(chan struct{}) @@ -380,9 +385,8 @@ func TestClientRecordSocketError(t *testing.T) { "tls", } { t.Run(transport, func(t *testing.T) { - l, err := net.Listen("tcp", "localhost:8554") - require.NoError(t, err) - defer l.Close() + var l net.Listener + var err error var scheme string if transport == "tls" { @@ -392,9 +396,15 @@ func TestClientRecordSocketError(t *testing.T) { cert, err = tls.X509KeyPair(serverCert, serverKey) require.NoError(t, err) - l = tls.NewListener(l, &tls.Config{Certificates: []tls.Certificate{cert}}) + l, err = tls.Listen("tcp", "localhost:8554", &tls.Config{Certificates: []tls.Certificate{cert}}) + require.NoError(t, err) + defer l.Close() } else { scheme = "rtsp" + + l, err = net.Listen("tcp", "localhost:8554") + require.NoError(t, err) + defer l.Close() } serverDone := make(chan struct{})