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:
parent
cadc6b3ea7
commit
75f518a827
|
@ -239,6 +239,10 @@ components:
|
|||
type: boolean
|
||||
fallback:
|
||||
type: string
|
||||
srtPublishPassphrase:
|
||||
type: string
|
||||
srtReadPassphrase:
|
||||
type: string
|
||||
|
||||
# RTSP
|
||||
sourceProtocol:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue