Support SRT encryption passphrases on configured paths (#2385)

* Support SRT encryption passphrases on configured paths

* Fix namespace; make passphrase use more clear

* Updates from Alessandro's feedback and lint output

* Fix lint findings

* Update lint.yml

* Update lint.yml

* Fix lint findings

* Add SRT passphrase tests for conf/path

* Reformat test to pass lint

* More changes to pass lint

* More changes to pass lint

* Use safeConf to prevent race conditions

* update API docs

* split configuration checks from connection checks

* add tests

* rename publishSRTPassphrase into srtPublishPassphrase, readSRTPassphrase into srtReadPassphrase

* remove redundant alias

---------

Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:
rmcnew 2023-09-23 04:55:45 -06:00 committed by GitHub
parent cadc6b3ea7
commit 75f518a827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 85 deletions

View File

@ -239,6 +239,10 @@ components:
type: boolean
fallback:
type: string
srtPublishPassphrase:
type: string
srtReadPassphrase:
type: string
# RTSP
sourceProtocol:

View File

@ -301,6 +301,20 @@ func TestConfErrors(t *testing.T) {
" source: rpiCamera\n",
"'rpiCamera' with same camera ID 0 is used as source in two paths, 'cam1' and 'cam2'",
},
{
"invalid srt publish passphrase",
"paths:\n" +
" mypath:\n" +
" srtPublishPassphrase: a\n",
`invalid 'srtPublishPassphrase': must be between 10 and 79 characters`,
},
{
"invalid srt read passphrase",
"paths:\n" +
" mypath:\n" +
" srtReadPassphrase: a\n",
`invalid 'readRTPassphrase': must be between 10 and 79 characters`,
},
} {
t.Run(ca.name, func(t *testing.T) {
tmpf, err := writeTempFile([]byte(ca.conf))

View File

@ -38,6 +38,16 @@ func IsValidPathName(name string) error {
return nil
}
func srtCheckPassphrase(passphrase string) error {
switch {
case len(passphrase) < 10 || len(passphrase) > 79:
return fmt.Errorf("must be between 10 and 79 characters")
default:
return nil
}
}
// PathConf is a path configuration.
type PathConf struct {
Regexp *regexp.Regexp `json:"-"`
@ -63,6 +73,8 @@ type PathConf struct {
OverridePublisher bool `json:"overridePublisher"`
DisablePublisherOverride bool `json:"disablePublisherOverride"` // deprecated
Fallback string `json:"fallback"`
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
SRTReadPassphrase string `json:"srtReadPassphrase"`
// RTSP
SourceProtocol SourceProtocol `json:"sourceProtocol"`
@ -362,6 +374,21 @@ func (pconf *PathConf) check(conf *Conf, name string) error {
}
}
// SRT
if pconf.SRTPublishPassphrase != "" {
err := srtCheckPassphrase(pconf.SRTPublishPassphrase)
if err != nil {
return fmt.Errorf("invalid 'srtPublishPassphrase': %v", err)
}
}
if pconf.SRTReadPassphrase != "" {
err := srtCheckPassphrase(pconf.SRTReadPassphrase)
if err != nil {
return fmt.Errorf("invalid 'readRTPassphrase': %v", err)
}
}
// Hooks
if pconf.RunOnInit != "" && pconf.Regexp != nil {

View File

@ -31,6 +31,23 @@ func durationGoToMPEGTS(v time.Duration) int64 {
return int64(v.Seconds() * 90000)
}
func srtCheckPassphrase(connReq srt.ConnRequest, passphrase string) error {
if passphrase == "" {
return nil
}
if !connReq.IsEncrypted() {
return fmt.Errorf("connection is encrypted, but not passphrase is defined in configuration")
}
err := connReq.SetPassphrase(passphrase)
if err != nil {
return fmt.Errorf("invalid passphrase")
}
return nil
}
type srtConnState int
const (
@ -222,6 +239,11 @@ func (c *srtConn) runPublish(req srtNewConnReq, pathName string, user string, pa
defer res.path.removePublisher(pathRemovePublisherReq{author: c})
err := srtCheckPassphrase(req.connReq, res.path.conf.SRTPublishPassphrase)
if err != nil {
return false, err
}
sconn, err := c.exchangeRequestWithConn(req)
if err != nil {
return true, err
@ -314,6 +336,11 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
defer res.path.removeReader(pathRemoveReaderReq{author: c})
err := srtCheckPassphrase(req.connReq, res.path.conf.SRTReadPassphrase)
if err != nil {
return false, err
}
sconn, err := c.exchangeRequestWithConn(req)
if err != nil {
return true, err

View File

@ -11,108 +11,136 @@ import (
)
func TestSRTServer(t *testing.T) {
p, ok := newInstance("paths:\n" +
" all:\n")
require.Equal(t, true, ok)
defer p.Close()
for _, ca := range []string{
"no passphrase",
"publish passphrase",
"read passphrase",
} {
t.Run(ca, func(t *testing.T) {
conf := "paths:\n" +
" all:\n"
conf := srt.DefaultConfig()
address, err := conf.UnmarshalURL("srt://localhost:8890?streamid=publish:mypath")
require.NoError(t, err)
switch ca {
case "publish passphrase":
conf += " srtPublishPassphrase: 123456789abcde"
err = conf.Validate()
require.NoError(t, err)
case "read passphrase":
conf += " srtReadPassphrase: 123456789abcde"
}
publisher, err := srt.Dial("srt", address, conf)
require.NoError(t, err)
defer publisher.Close()
p, ok := newInstance(conf)
require.Equal(t, true, ok)
defer p.Close()
track := &mpegts.Track{
Codec: &mpegts.CodecH264{},
}
u := "srt://localhost:8890?streamid=publish:mypath"
if ca == "publish passphrase" {
u += "&passphrase=123456789abcde"
}
bw := bufio.NewWriter(publisher)
w := mpegts.NewWriter(bw, []*mpegts.Track{track})
require.NoError(t, err)
srtConf := srt.DefaultConfig()
address, err := srtConf.UnmarshalURL(u)
require.NoError(t, err)
err = w.WriteH26x(track, 0, 0, true, [][]byte{
{ // SPS
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,
},
{ // PPS
0x08, 0x06, 0x07, 0x08,
},
{ // IDR
0x05, 1,
},
})
require.NoError(t, err)
err = srtConf.Validate()
require.NoError(t, err)
err = bw.Flush()
require.NoError(t, err)
publisher, err := srt.Dial("srt", address, srtConf)
require.NoError(t, err)
defer publisher.Close()
time.Sleep(500 * time.Millisecond)
track := &mpegts.Track{
Codec: &mpegts.CodecH264{},
}
conf = srt.DefaultConfig()
address, err = conf.UnmarshalURL("srt://localhost:8890?streamid=read:mypath")
require.NoError(t, err)
bw := bufio.NewWriter(publisher)
w := mpegts.NewWriter(bw, []*mpegts.Track{track})
require.NoError(t, err)
err = conf.Validate()
require.NoError(t, err)
err = w.WriteH26x(track, 0, 0, true, [][]byte{
{ // SPS
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,
},
{ // PPS
0x08, 0x06, 0x07, 0x08,
},
{ // IDR
0x05, 1,
},
})
require.NoError(t, err)
reader, err := srt.Dial("srt", address, conf)
require.NoError(t, err)
defer reader.Close()
err = bw.Flush()
require.NoError(t, err)
err = w.WriteH26x(track, 2*90000, 1*90000, true, [][]byte{
{ // IDR
0x05, 2,
},
})
require.NoError(t, err)
time.Sleep(500 * time.Millisecond)
err = bw.Flush()
require.NoError(t, err)
u = "srt://localhost:8890?streamid=read:mypath"
if ca == "read passphrase" {
u += "&passphrase=123456789abcde"
}
r, err := mpegts.NewReader(reader)
require.NoError(t, err)
srtConf = srt.DefaultConfig()
address, err = srtConf.UnmarshalURL(u)
require.NoError(t, err)
require.Equal(t, []*mpegts.Track{{
PID: 256,
Codec: &mpegts.CodecH264{},
}}, r.Tracks())
err = srtConf.Validate()
require.NoError(t, err)
received := false
reader, err := srt.Dial("srt", address, srtConf)
require.NoError(t, err)
defer reader.Close()
r.OnDataH26x(r.Tracks()[0], func(pts int64, dts int64, au [][]byte) error {
require.Equal(t, int64(0), pts)
require.Equal(t, int64(0), dts)
require.Equal(t, [][]byte{
{ // SPS
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,
},
{ // PPS
0x08, 0x06, 0x07, 0x08,
},
{ // IDR
0x05, 1,
},
}, au)
received = true
return nil
})
err = w.WriteH26x(track, 2*90000, 1*90000, true, [][]byte{
{ // IDR
0x05, 2,
},
})
require.NoError(t, err)
for {
err = r.Read()
require.NoError(t, err)
if received {
break
}
err = bw.Flush()
require.NoError(t, err)
r, err := mpegts.NewReader(reader)
require.NoError(t, err)
require.Equal(t, []*mpegts.Track{{
PID: 256,
Codec: &mpegts.CodecH264{},
}}, r.Tracks())
received := false
r.OnDataH26x(r.Tracks()[0], func(pts int64, dts int64, au [][]byte) error {
require.Equal(t, int64(0), pts)
require.Equal(t, int64(0), dts)
require.Equal(t, [][]byte{
{ // SPS
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,
},
{ // PPS
0x08, 0x06, 0x07, 0x08,
},
{ // IDR
0x05, 1,
},
}, au)
received = true
return nil
})
for {
err = r.Read()
require.NoError(t, err)
if received {
break
}
}
})
}
}

View File

@ -338,6 +338,10 @@ paths:
# if no one is publishing, redirect readers to this path.
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
fallback:
# SRT encryption passphrase required to publish on this path
srtPublishPassphrase:
# SRT encryption passphrase require to read from this path
srtReadPassphrase:
###############################################
# RTSP path settings (when source is a RTSP or a RTSPS URL)