support encrypting the configuration file

This commit is contained in:
aler9 2021-01-16 15:42:54 +01:00
parent 903842484e
commit 6b1643940e
7 changed files with 152 additions and 60 deletions

View file

@ -30,6 +30,7 @@ Features:
* [Configuration](#configuration)
* [Encryption](#encryption)
* [Authentication](#authentication)
* [Encrypt the configuration](#encrypt-the-configuration)
* [RTSP proxy mode](#rtsp-proxy-mode)
* [Publish a webcam](#publish-a-webcam)
* [Publish a Raspberry Pi Camera](#publish-a-raspberry-pi-camera)
@ -217,6 +218,26 @@ paths:
**WARNING**: enable encryption or use a VPN to ensure that no one is intercepting the credentials.
### Encrypt the configuration
The configuration file can be entirely encrypted for security purposes.
An online encryption tool is [available here](https://play.golang.org/p/rX29jwObNe4).
The encryption procedure is the following:
1. NaCL's `crypto_secretbox` function is applied to the content of the configuration. NaCL is a cryptographic library available for [C/C++](https://nacl.cr.yp.to/secretbox.html), [Go](https://pkg.go.dev/golang.org/x/crypto/nacl/secretbox), [C#](https://github.com/somdoron/NaCl.net) and many other languages;
2. The string is prefixed with the nonce;
3. The string is encoded with base64.
After performing the encryption, it's enough to put the base64-encoded result into the configuration file, and launch the server with the `RTSP_CONFKEY` variable:
```
RTSP_CONFKEY=mykey ./rtsp-simple-server
```
### RTSP proxy mode
_rtsp-simple-server_ is also a RTSP proxy, that is usually deployed in one of these scenarios:

1
go.mod
View file

@ -11,6 +11,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/notedit/rtmp v0.0.2
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v2 v2.2.8
)

9
go.sum
View file

@ -29,8 +29,17 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View file

@ -1,12 +1,15 @@
package conf
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/headers"
"golang.org/x/crypto/nacl/secretbox"
"gopkg.in/yaml.v2"
"github.com/aler9/rtsp-simple-server/internal/confenv"
@ -23,6 +26,25 @@ const (
EncryptionStrict
)
func decrypt(key string, byts []byte) ([]byte, error) {
enc, err := base64.StdEncoding.DecodeString(string(byts))
if err != nil {
return nil, err
}
var secretKey [32]byte
copy(secretKey[:], key)
var decryptNonce [24]byte
copy(decryptNonce[:], enc[:24])
decrypted, ok := secretbox.Open(nil, enc[24:], &decryptNonce, &secretKey)
if !ok {
return nil, fmt.Errorf("decryption error")
}
return decrypted, nil
}
// Conf is the main program configuration.
type Conf struct {
LogLevel string `yaml:"logLevel"`
@ -228,15 +250,21 @@ func Load(fpath string) (*Conf, bool, error) {
}
}
f, err := os.Open(fpath)
byts, err := ioutil.ReadFile(fpath)
if err != nil {
return false, err
return true, err
}
defer f.Close()
err = yaml.NewDecoder(f).Decode(conf)
if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok {
byts, err = decrypt(key, byts)
if err != nil {
return true, err
}
}
err = yaml.Unmarshal(byts, conf)
if err != nil {
return false, err
return true, err
}
return true, nil

View file

@ -3,6 +3,7 @@ package conf
import (
"encoding/json"
"fmt"
"net"
"net/url"
"regexp"
"strings"
@ -12,9 +13,56 @@ import (
"github.com/aler9/gortsplib/pkg/base"
)
const userPassSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,{,}"
var reUserPass = regexp.MustCompile(`^[a-zA-Z0-9!\$\(\)\*\+\.;<=>\[\]\^_\-\{\}]+$`)
const userPassSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,{,}"
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/]+$`)
func parseIPCidrList(in []string) ([]interface{}, error) {
if len(in) == 0 {
return nil, nil
}
var ret []interface{}
for _, t := range in {
_, ipnet, err := net.ParseCIDR(t)
if err == nil {
ret = append(ret, ipnet)
continue
}
ip := net.ParseIP(t)
if ip != nil {
ret = append(ret, ip)
continue
}
return nil, fmt.Errorf("unable to parse ip/network '%s'", t)
}
return ret, nil
}
// CheckPathName checks if a path name is valid.
func CheckPathName(name string) error {
if name == "" {
return fmt.Errorf("cannot be empty")
}
if name[0] == '/' {
return fmt.Errorf("can't begin with a slash")
}
if name[len(name)-1] == '/' {
return fmt.Errorf("can't end with a slash")
}
if !rePathName.MatchString(name) {
return fmt.Errorf("can contain only alfanumeric characters, underscore, minus or slash")
}
return nil
}
// PathConf is a path configuration.
type PathConf struct {

View file

@ -1,54 +0,0 @@
package conf
import (
"fmt"
"net"
"regexp"
)
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/]+$`)
// CheckPathName check if a path name is valid.
func CheckPathName(name string) error {
if name == "" {
return fmt.Errorf("cannot be empty")
}
if name[0] == '/' {
return fmt.Errorf("can't begin with a slash")
}
if name[len(name)-1] == '/' {
return fmt.Errorf("can't end with a slash")
}
if !rePathName.MatchString(name) {
return fmt.Errorf("can contain only alfanumeric characters, underscore, minus or slash")
}
return nil
}
func parseIPCidrList(in []string) ([]interface{}, error) {
if len(in) == 0 {
return nil, nil
}
var ret []interface{}
for _, t := range in {
_, ipnet, err := net.ParseCIDR(t)
if err == nil {
ret = append(ret, ipnet)
continue
}
ip := net.ParseIP(t)
if ip != nil {
ret = append(ret, ip)
continue
}
return nil, fmt.Errorf("unable to parse ip/network '%s'", t)
}
return ret, nil
}

View file

@ -1,6 +1,9 @@
package main
import (
"crypto/rand"
"encoding/base64"
"io"
"io/ioutil"
"net"
"os"
@ -13,6 +16,7 @@ import (
"github.com/aler9/gortsplib"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/nacl/secretbox"
"github.com/aler9/rtsp-simple-server/internal/conf"
)
@ -245,6 +249,41 @@ func TestEnvironmentNoFile(t *testing.T) {
}, pa)
}
func TestEncryptedConf(t *testing.T) {
key := "testing123testin"
plaintext := `
paths:
path1:
path2:
`
encryptedConf := func() string {
var secretKey [32]byte
copy(secretKey[:], key[:])
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
encrypted := secretbox.Seal(nonce[:], []byte(plaintext), &nonce, &secretKey)
return base64.StdEncoding.EncodeToString(encrypted)
}()
os.Setenv("RTSP_CONFKEY", key)
defer os.Unsetenv("RTSP_CONFKEY")
p, ok := testProgram(encryptedConf)
require.Equal(t, true, ok)
defer p.close()
_, ok = p.conf.Paths["path1"]
require.Equal(t, true, ok)
_, ok = p.conf.Paths["path2"]
require.Equal(t, true, ok)
}
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM