implement AuthServer and AuthClient

This commit is contained in:
aler9
2020-02-16 15:25:50 +01:00
parent 2f23f19aca
commit 162fc911ce
5 changed files with 194 additions and 42 deletions

71
authclient.go Normal file
View File

@@ -0,0 +1,71 @@
package gortsplib
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
)
func md5Hex(in string) string {
h := md5.New()
h.Write([]byte(in))
return hex.EncodeToString(h.Sum(nil))
}
// AuthClient is an object that helps a client sending its credentials to a server.
type AuthClient struct {
user string
pass string
realm string
nonce string
}
// NewAuthClient allocates an AuthClient.
// header is the WWW-Authenticate header provided by the server.
func NewAuthClient(header []string, user string, pass string) (*AuthClient, error) {
headerAuthDigest := func() string {
for _, v := range header {
if strings.HasPrefix(v, "Digest ") {
return v
}
}
return ""
}()
if headerAuthDigest == "" {
return nil, fmt.Errorf("Authentication/Digest header not provided")
}
auth, err := ReadHeaderAuth(headerAuthDigest)
if err != nil {
return nil, err
}
nonce, ok := auth.Values["nonce"]
if !ok {
return nil, fmt.Errorf("nonce not provided")
}
realm, ok := auth.Values["realm"]
if !ok {
return nil, fmt.Errorf("realm not provided")
}
return &AuthClient{
user: user,
pass: pass,
realm: realm,
nonce: nonce,
}, nil
}
// GenerateHeader generates an Authorization Header that allows to authenticate a request with
// the given method and path.
func (ac *AuthClient) GenerateHeader(method string, path string) []string {
ha1 := md5Hex(ac.user + ":" + ac.realm + ":" + ac.pass)
ha2 := md5Hex(method + ":" + path)
response := md5Hex(ha1 + ":" + ac.nonce + ":" + ha2)
return []string{fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
ac.user, ac.realm, ac.nonce, path, response)}
}

View File

@@ -1,38 +0,0 @@
package gortsplib
import (
"crypto/md5"
"encoding/hex"
"fmt"
)
func md5Hex(in string) string {
h := md5.New()
h.Write([]byte(in))
return hex.EncodeToString(h.Sum(nil))
}
type authClientProvider struct {
user string
pass string
realm string
nonce string
}
func newAuthClientProvider(user string, pass string, realm string, nonce string) *authClientProvider {
return &authClientProvider{
user: user,
pass: pass,
realm: realm,
nonce: nonce,
}
}
func (ap *authClientProvider) generateHeader(method string, path string) string {
ha1 := md5Hex(ap.user + ":" + ap.realm + ":" + ap.pass)
ha2 := md5Hex(method + ":" + path)
response := md5Hex(ha1 + ":" + ap.nonce + ":" + ha2)
return fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
ap.user, ap.realm, ap.nonce, path, response)
}

98
authserver.go Normal file
View File

@@ -0,0 +1,98 @@
package gortsplib
import (
"crypto/rand"
"encoding/hex"
"fmt"
)
// AuthServer is an object that helps a server validating the credentials of a client.
type AuthServer struct {
nonce string
realm string
user string
pass string
}
// NewAuthServer allocates an AuthServer.
func NewAuthServer(user string, pass string) *AuthServer {
nonceByts := make([]byte, 16)
rand.Read(nonceByts)
nonce := hex.EncodeToString(nonceByts)
return &AuthServer{
nonce: nonce,
realm: "IPCAM",
user: user,
pass: pass,
}
}
// GenerateHeader generates the WWW-Authenticate header needed by a client to log in.
func (as *AuthServer) GenerateHeader() []string {
return []string{fmt.Sprintf("Digest nonce=%s, realm=%s", as.nonce, as.realm)}
}
// ValidateHeader validates the Authorization header sent by a client after receiving the
// WWW-Authenticate header provided by GenerateHeader().
func (as *AuthServer) ValidateHeader(header []string, method string, path string) error {
if len(header) != 1 {
return fmt.Errorf("Authorization header not provided")
}
auth, err := ReadHeaderAuth(header[0])
if err != nil {
return err
}
inNonce, ok := auth.Values["nonce"]
if !ok {
return fmt.Errorf("nonce not provided")
}
inRealm, ok := auth.Values["realm"]
if !ok {
return fmt.Errorf("realm not provided")
}
inUsername, ok := auth.Values["username"]
if !ok {
return fmt.Errorf("username not provided")
}
inUri, ok := auth.Values["uri"]
if !ok {
return fmt.Errorf("uri not provided")
}
inResponse, ok := auth.Values["response"]
if !ok {
return fmt.Errorf("response not provided")
}
if inNonce != as.nonce {
return fmt.Errorf("wrong nonce")
}
if inRealm != as.realm {
return fmt.Errorf("wrong realm")
}
if inUsername != as.user {
return fmt.Errorf("wrong username")
}
if inUri != path {
return fmt.Errorf("wrong uri")
}
ha1 := md5Hex(as.user + ":" + as.realm + ":" + as.pass)
ha2 := md5Hex(method + ":" + path)
response := md5Hex(ha1 + ":" + as.nonce + ":" + ha2)
if inResponse != response {
return fmt.Errorf("wrong response")
}
return nil
}

19
authserver_test.go Normal file
View File

@@ -0,0 +1,19 @@
package gortsplib
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAuthClientServer(t *testing.T) {
as := NewAuthServer("testuser", "testpass")
wwwAuthenticate := as.GenerateHeader()
ac, err := NewAuthClient(wwwAuthenticate, "testuser", "testpass")
require.NoError(t, err)
authorization := ac.GenerateHeader("ANNOUNCE", "rtsp://myhost/mypath")
err = as.ValidateHeader(authorization, "ANNOUNCE", "rtsp://myhost/mypath")
require.NoError(t, err)
}

View File

@@ -14,7 +14,7 @@ type ConnClient struct {
session string
cseqEnabled bool
cseq int
authProv *authClientProvider
authProv *AuthClient
}
// NewConnClient allocates a ConnClient.
@@ -44,8 +44,10 @@ func (c *ConnClient) EnableCseq() {
// SetCredentials allows to automatically insert the Authenticate header into every outgoing request.
// The content of the header is computed with the given user, password, realm and nonce.
func (c *ConnClient) SetCredentials(user string, pass string, realm string, nonce string) {
c.authProv = newAuthClientProvider(user, pass, realm, nonce)
func (c *ConnClient) SetCredentials(wwwAuthenticateHeader []string, user string, pass string) error {
var err error
c.authProv, err = NewAuthClient(wwwAuthenticateHeader, user, pass)
return err
}
// WriteRequest writes a Request.
@@ -67,7 +69,7 @@ func (c *ConnClient) WriteRequest(req *Request) error {
if req.Header == nil {
req.Header = make(Header)
}
req.Header["Authorization"] = []string{c.authProv.generateHeader(req.Method, req.Url)}
req.Header["Authorization"] = c.authProv.GenerateHeader(req.Method, req.Url)
}
return req.write(c.bw)
}