From 61318d7f96caeafd69c5869e57d7068f342f726c Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 14 Dec 2020 22:49:47 +0100 Subject: [PATCH] implement client TLS support --- README.md | 1 + clientconf.go | 33 ++++++++++++---- clientconf_test.go | 42 ++++++++++++++++---- clientconn.go | 2 +- examples/client-query.go | 2 +- examples/client-read-partial.go | 2 +- server.go | 10 +++-- serverconf_test.go | 20 +++++----- testimages/rtsp-simple-server/Dockerfile | 13 +++++- testimages/rtsp-simple-server/start.sh | 50 ++++++++++++++++++++++++ 10 files changed, 141 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 16897c32..dccbe3da 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Features: * Client * Read streams from servers with UDP or TCP * Publish streams to servers with UDP or TCP + * Encrypt streams with TLS (RTSPS) * Query servers about published streams * Read only selected tracks of a stream * Pause reading or publishing without disconnecting from the server diff --git a/clientconf.go b/clientconf.go index 844e7959..87534050 100644 --- a/clientconf.go +++ b/clientconf.go @@ -2,6 +2,7 @@ package gortsplib import ( "bufio" + "crypto/tls" "fmt" "net" "strings" @@ -18,8 +19,8 @@ import ( var DefaultClientConf = ClientConf{} // Dial connects to a server. -func Dial(host string) (*ClientConn, error) { - return DefaultClientConf.Dial(host) +func Dial(scheme string, host string) (*ClientConn, error) { + return DefaultClientConf.Dial(scheme, host) } // DialRead connects to a server and starts reading all tracks. @@ -40,6 +41,10 @@ type ClientConf struct { // It defaults to nil. StreamProtocol *StreamProtocol + // A TLS configuration to connect to TLS (RTSPS) servers. + // It defaults to &tls.Config{InsecureSkipVerify:true} + TLSConfig *tls.Config + // timeout of read operations. // It defaults to 10 seconds. ReadTimeout time.Duration @@ -74,7 +79,10 @@ type ClientConf struct { } // Dial connects to a server. -func (c ClientConf) Dial(host string) (*ClientConn, error) { +func (c ClientConf) Dial(scheme string, host string) (*ClientConn, error) { + if c.TLSConfig == nil { + c.TLSConfig = &tls.Config{InsecureSkipVerify: true} + } if c.ReadTimeout == 0 { c.ReadTimeout = 10 * time.Second } @@ -91,6 +99,10 @@ func (c ClientConf) Dial(host string) (*ClientConn, error) { c.ListenPacket = net.ListenPacket } + if scheme != "rtsp" && scheme != "rtsps" { + return nil, fmt.Errorf("unsupported scheme '%s'", scheme) + } + if !strings.Contains(host, ":") { host += ":554" } @@ -100,11 +112,18 @@ func (c ClientConf) Dial(host string) (*ClientConn, error) { return nil, err } + conn := func() net.Conn { + if scheme == "rtsps" { + return tls.Client(nconn, c.TLSConfig) + } + return nconn + }() + return &ClientConn{ conf: c, nconn: nconn, - br: bufio.NewReaderSize(nconn, clientReadBufferSize), - bw: bufio.NewWriterSize(nconn, clientWriteBufferSize), + br: bufio.NewReaderSize(conn, clientReadBufferSize), + bw: bufio.NewWriterSize(conn, clientWriteBufferSize), udpRtpListeners: make(map[int]*clientConnUDPListener), udpRtcpListeners: make(map[int]*clientConnUDPListener), rtcpReceivers: make(map[int]*rtcpreceiver.RtcpReceiver), @@ -122,7 +141,7 @@ func (c ClientConf) DialRead(address string) (*ClientConn, error) { return nil, err } - conn, err := c.Dial(u.Host) + conn, err := c.Dial(u.Scheme, u.Host) if err != nil { return nil, err } @@ -163,7 +182,7 @@ func (c ClientConf) DialPublish(address string, tracks Tracks) (*ClientConn, err return nil, err } - conn, err := c.Dial(u.Host) + conn, err := c.Dial(u.Scheme, u.Host) if err != nil { return nil, err } diff --git a/clientconf_test.go b/clientconf_test.go index 24cb51b1..c990f1e7 100644 --- a/clientconf_test.go +++ b/clientconf_test.go @@ -60,12 +60,38 @@ func (c *container) wait() int { } func TestClientDialRead(t *testing.T) { - for _, proto := range []string{ - "udp", - "tcp", + for _, ca := range []struct { + encrypted bool + proto string + }{ + {false, "udp"}, + {false, "tcp"}, + {true, "tcp"}, } { - t.Run(proto, func(t *testing.T) { - cnt1, err := newContainer("rtsp-simple-server", "server", []string{"{}"}) + encryptedStr := func() string { + if ca.encrypted { + return "encrypted" + } + return "plain" + }() + + t.Run(encryptedStr+"_"+ca.proto, func(t *testing.T) { + var scheme string + var port string + var serverConf string + if !ca.encrypted { + scheme = "rtsp" + port = "8554" + serverConf = "{}" + } else { + scheme = "rtsps" + port = "8555" + serverConf = "readTimeout: 20s\n" + + "protocols: [tcp]\n" + + "encryption: yes\n" + } + + cnt1, err := newContainer("rtsp-simple-server", "server", []string{serverConf}) require.NoError(t, err) defer cnt1.close() @@ -78,7 +104,7 @@ func TestClientDialRead(t *testing.T) { "-c", "copy", "-f", "rtsp", "-rtsp_transport", "udp", - "rtsp://localhost:8554/teststream", + scheme + "://localhost:" + port + "/teststream", }) require.NoError(t, err) defer cnt2.close() @@ -87,7 +113,7 @@ func TestClientDialRead(t *testing.T) { conf := ClientConf{ StreamProtocol: func() *StreamProtocol { - if proto == "udp" { + if ca.proto == "udp" { v := StreamProtocolUDP return &v } @@ -96,7 +122,7 @@ func TestClientDialRead(t *testing.T) { }(), } - conn, err := conf.DialRead("rtsp://localhost:8554/teststream") + conn, err := conf.DialRead(scheme + "://localhost:" + port + "/teststream") require.NoError(t, err) var firstFrame int32 diff --git a/clientconn.go b/clientconn.go index 616719e8..f9876994 100644 --- a/clientconn.go +++ b/clientconn.go @@ -318,7 +318,7 @@ func (c *ClientConn) Describe(u *base.URL) (Tracks, *base.Response, error) { return nil, nil, err } - nc, err := c.conf.Dial(u.Host) + nc, err := c.conf.Dial(u.Scheme, u.Host) if err != nil { return nil, nil, err } diff --git a/examples/client-query.go b/examples/client-query.go index 9d11abb9..0423967a 100644 --- a/examples/client-query.go +++ b/examples/client-query.go @@ -19,7 +19,7 @@ func main() { panic(err) } - conn, err := gortsplib.Dial(u.Host) + conn, err := gortsplib.Dial(u.Scheme, u.Host) if err != nil { panic(err) } diff --git a/examples/client-read-partial.go b/examples/client-read-partial.go index 612a5db7..8eb1a638 100644 --- a/examples/client-read-partial.go +++ b/examples/client-read-partial.go @@ -21,7 +21,7 @@ func main() { panic(err) } - conn, err := gortsplib.Dial(u.Host) + conn, err := gortsplib.Dial(u.Scheme, u.Host) if err != nil { panic(err) } diff --git a/server.go b/server.go index 3a5aa5e4..e1360353 100644 --- a/server.go +++ b/server.go @@ -24,10 +24,12 @@ func (s *Server) Accept() (*ServerConn, error) { return nil, err } - conn := nconn - if s.conf.TLSConfig != nil { - conn = tls.Server(conn, s.conf.TLSConfig) - } + conn := func() net.Conn { + if s.conf.TLSConfig != nil { + return tls.Server(nconn, s.conf.TLSConfig) + } + return nconn + }() return &ServerConn{ s: s, diff --git a/serverconf_test.go b/serverconf_test.go index 06bc7171..966a9b1c 100644 --- a/serverconf_test.go +++ b/serverconf_test.go @@ -271,18 +271,18 @@ y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD func TestServerPublishReadTCP(t *testing.T) { for _, ca := range []struct { + encrypted bool publisher string reader string - encrypted bool }{ - {"ffmpeg", "ffmpeg", false}, - {"ffmpeg", "ffmpeg", true}, - {"ffmpeg", "gstreamer", false}, - {"ffmpeg", "gstreamer", true}, - {"gstreamer", "ffmpeg", false}, - {"gstreamer", "ffmpeg", true}, - {"gstreamer", "gstreamer", false}, - {"gstreamer", "gstreamer", true}, + {false, "ffmpeg", "ffmpeg"}, + {false, "ffmpeg", "gstreamer"}, + {false, "gstreamer", "ffmpeg"}, + {false, "gstreamer", "gstreamer"}, + {true, "ffmpeg", "ffmpeg"}, + {true, "ffmpeg", "gstreamer"}, + {true, "gstreamer", "ffmpeg"}, + {true, "gstreamer", "gstreamer"}, } { encryptedStr := func() string { if ca.encrypted { @@ -291,7 +291,7 @@ func TestServerPublishReadTCP(t *testing.T) { return "plain" }() - t.Run(ca.publisher+"_"+ca.reader+"_"+encryptedStr, func(t *testing.T) { + t.Run(encryptedStr+"_"+ca.publisher+"_"+ca.reader, func(t *testing.T) { var proto string var tlsConf *tls.Config if !ca.encrypted { diff --git a/testimages/rtsp-simple-server/Dockerfile b/testimages/rtsp-simple-server/Dockerfile index df1a267d..7875157e 100644 --- a/testimages/rtsp-simple-server/Dockerfile +++ b/testimages/rtsp-simple-server/Dockerfile @@ -1,5 +1,14 @@ -############################ -FROM aler9/rtsp-simple-server:latest AS server +#################################### +FROM amd64/golang:1.15-alpine3.12 AS server + +RUN apk --no-cache add git + +RUN git clone https://github.com/aler9/rtsp-simple-server + +RUN cd rtsp-simple-server \ + && CGO_ENABLED=0 \ + go build -o /rtsp-simple-server . + ############################ FROM alpine:3.12 diff --git a/testimages/rtsp-simple-server/start.sh b/testimages/rtsp-simple-server/start.sh index 9a93b80c..295b2feb 100644 --- a/testimages/rtsp-simple-server/start.sh +++ b/testimages/rtsp-simple-server/start.sh @@ -2,4 +2,54 @@ echo "$@" > /rtsp-simple-server.yml +echo "-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMTMxNzQ0NThaFw0zMDEy +MTExNzQ0NThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDG8DyyS51810GsGwgWr5rjJK7OE1kTTLSNEEKax8Bj +zOyiaz8rA2JGl2VUEpi2UjDr9Cm7nd+YIEVs91IIBOb7LGqObBh1kGF3u5aZxLkv +NJE+HrLVvUhaDobK2NU+Wibqc/EI3DfUkt1rSINvv9flwTFu1qHeuLWhoySzDKEp +OzYxpFhwjVSokZIjT4Red3OtFz7gl2E6OAWe2qoh5CwLYVdMWtKR0Xuw3BkDPk9I +qkQKx3fqv97LPEzhyZYjDT5WvGrgZ1WDAN3booxXF3oA1H3GHQc4m/vcLatOtb8e +nI59gMQLEbnp08cl873bAuNuM95EZieXTHNbwUnq5iybAgMBAAGjUzBRMB0GA1Ud +DgQWBBQBKhJh8eWu0a4au9X/2fKhkFX2vjAfBgNVHSMEGDAWgBQBKhJh8eWu0a4a +u9X/2fKhkFX2vjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj +3aCW0YPKukYgVK9cwN0IbVy/D0C1UPT4nupJcy/E0iC7MXPZ9D/SZxYQoAkdptdO +xfI+RXkpQZLdODNx9uvV+cHyZHZyjtE5ENu/i5Rer2cWI/mSLZm5lUQyx+0KZ2Yu +tEI1bsebDK30msa8QSTn0WidW9XhFnl3gRi4wRdimcQapOWYVs7ih+nAlSvng7NI +XpAyRs8PIEbpDDBMWnldrX4TP6EWYUi49gCp8OUDRREKX3l6Ls1vZ02F34yHIt/7 +7IV/XSKG096bhW+icKBWV0IpcEsgTzPK1J1hMxgjhzIMxGboAeUU+kidthOob6Sd +XQxaORfgM//NzX9LhUPk +-----END CERTIFICATE-----" > /server.crt + +echo "-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAxvA8skudfNdBrBsIFq+a4ySuzhNZE0y0jRBCmsfAY8zsoms/ +KwNiRpdlVBKYtlIw6/Qpu53fmCBFbPdSCATm+yxqjmwYdZBhd7uWmcS5LzSRPh6y +1b1IWg6GytjVPlom6nPxCNw31JLda0iDb7/X5cExbtah3ri1oaMkswyhKTs2MaRY +cI1UqJGSI0+EXndzrRc+4JdhOjgFntqqIeQsC2FXTFrSkdF7sNwZAz5PSKpECsd3 +6r/eyzxM4cmWIw0+Vrxq4GdVgwDd26KMVxd6ANR9xh0HOJv73C2rTrW/HpyOfYDE +CxG56dPHJfO92wLjbjPeRGYnl0xzW8FJ6uYsmwIDAQABAoIBACi0BKcyQ3HElSJC +kaAao+Uvnzh4yvPg8Nwf5JDIp/uDdTMyIEWLtrLczRWrjGVZYbsVROinP5VfnPTT +kYwkfKINj2u+gC6lsNuPnRuvHXikF8eO/mYvCTur1zZvsQnF5kp4GGwIqr+qoPUP +bB0UMndG1PdpoMryHe+JcrvTrLHDmCeH10TqOwMsQMLHYLkowvxwJWsmTY7/Qr5S +Wm3PPpOcW2i0uyPVuyuv4yD1368fqnqJ8QFsQp1K6QtYsNnJ71Hut1/IoxK/e6hj +5Z+byKtHVtmcLnABuoOT7BhleJNFBksX9sh83jid4tMBgci+zXNeGmgqo2EmaWAb +agQslkECgYEA8B1rzjOHVQx/vwSzDa4XOrpoHQRfyElrGNz9JVBvnoC7AorezBXQ +M9WTHQIFTGMjzD8pb+YJGi3gj93VN51r0SmJRxBaBRh1ZZI9kFiFzngYev8POgD3 +ygmlS3kTHCNxCK/CJkB+/jMBgtPj5ygDpCWVcTSuWlQFphePkW7jaaECgYEA1Blz +ulqgAyJHZaqgcbcCsI2q6m527hVr9pjzNjIVmkwu38yS9RTCgdlbEVVDnS0hoifl ++jVMEGXjF3xjyMvL50BKbQUH+KAa+V4n1WGlnZOxX9TMny8MBjEuSX2+362vQ3BX +4vOlX00gvoc+sY+lrzvfx/OdPCHQGVYzoKCxhLsCgYA07HcviuIAV/HsO2/vyvhp +xF5gTu+BqNUHNOZDDDid+ge+Jre2yfQLCL8VPLXIQW3Jff53IH/PGl+NtjphuLvj +7UDJvgvpZZuymIojP6+2c3gJ3CASC9aR3JBnUzdoE1O9s2eaoMqc4scpe+SWtZYf +3vzSZ+cqF6zrD/Rf/M35IQKBgHTU4E6ShPm09CcoaeC5sp2WK8OevZw/6IyZi78a +r5Oiy18zzO97U/k6xVMy6F+38ILl/2Rn31JZDVJujniY6eSkIVsUHmPxrWoXV1HO +y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD +94TpAoGAY4/PejWQj9psZfAhyk5dRGra++gYRQ/gK1IIc1g+Dd2/BxbT/RHr05GK +6vwrfjsoRyMWteC1SsNs/CurjfQ/jqCfHNP5XPvxgd5Ec8sRJIiV7V5RTuWJsPu1 ++3K6cnKEyg+0ekYmLertRFIY6SwWmY1fyKgTvxudMcsBY7dC4xs= +-----END RSA PRIVATE KEY-----" > /server.key + exec /rtsp-simple-server