Skip to content

Commit

Permalink
Abstract exchanging keying material
Browse files Browse the repository at this point in the history
- Add an abstraction to exchange keying material between the DTLS
  and SRTP package.
- Construct & start the Session at the same time to avoid races.
  • Loading branch information
backkem committed Jan 29, 2019
1 parent 487b408 commit 93a8f4f
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 22 deletions.
45 changes: 45 additions & 0 deletions keying.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package srtp

const labelExtractorDtlsSrtp = "EXTRACTOR-dtls_srtp"

// KeyingMaterialExporter allows package SRTP to extract keying material
type KeyingMaterialExporter interface {
ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error)
}

// ExtractSessionKeysFromDTLS allows setting the Config SessionKeys by
// extracting them from DTLS. This behavior is defined in RFC5764:
// https://tools.ietf.org/html/rfc5764
func (c *Config) ExtractSessionKeysFromDTLS(exporter KeyingMaterialExporter, isClient bool) error {
keyingMaterial, err := exporter.ExportKeyingMaterial(labelExtractorDtlsSrtp, nil, (keyLen*2)+(saltLen*2))
if err != nil {
return err
}

offset := 0
clientWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...)
offset += keyLen

serverWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...)
offset += keyLen

clientWriteKey = append(clientWriteKey, keyingMaterial[offset:offset+saltLen]...)
offset += saltLen

serverWriteKey = append(serverWriteKey, keyingMaterial[offset:offset+saltLen]...)

if isClient {
c.Keys.LocalMasterKey = clientWriteKey[0:keyLen]
c.Keys.LocalMasterSalt = clientWriteKey[keyLen:]
c.Keys.RemoteMasterKey = serverWriteKey[0:keyLen]
c.Keys.RemoteMasterSalt = serverWriteKey[keyLen:]
return nil
}

c.Keys.LocalMasterKey = serverWriteKey[0:keyLen]
c.Keys.LocalMasterSalt = serverWriteKey[keyLen:]
c.Keys.RemoteMasterKey = clientWriteKey[0:keyLen]
c.Keys.RemoteMasterSalt = clientWriteKey[keyLen:]
return nil

}
70 changes: 70 additions & 0 deletions keying_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package srtp

import (
"bytes"
"crypto/rand"
"fmt"
"testing"
)

type mockKeyingMaterialExporter struct {
exported []byte
}

func (m *mockKeyingMaterialExporter) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) {
if label != labelExtractorDtlsSrtp {
return nil, fmt.Errorf("exporter called with wrong label: %s", label)
}

m.exported = make([]byte, length)
if _, err := rand.Read(m.exported); err != nil {
return nil, fmt.Errorf("failed to create random bytes: %v", err)
}

return m.exported, nil
}

func TestExtractSessionKeysFromDTLS(t *testing.T) {
tt := []struct {
config *Config
}{
{&Config{Profile: ProtectionProfileAes128CmHmacSha1_80}},
}

m := &mockKeyingMaterialExporter{}

for i, tc := range tt {
// Test client
err := tc.config.ExtractSessionKeysFromDTLS(m, true)
if err != nil {
t.Errorf("failed to extract keys for %d-client: %v", i, err)
}

keys := tc.config.Keys
clientMaterial := append([]byte{}, keys.LocalMasterKey...)
clientMaterial = append(clientMaterial, keys.RemoteMasterKey...)
clientMaterial = append(clientMaterial, keys.LocalMasterSalt...)
clientMaterial = append(clientMaterial, keys.RemoteMasterSalt...)

if !bytes.Equal(clientMaterial, m.exported) {
t.Errorf("material reconstruction failed for %d-client:\n%#v\nexpected\n%#v", i, clientMaterial, m.exported)
}

// Test server
err = tc.config.ExtractSessionKeysFromDTLS(m, false)
if err != nil {
t.Errorf("failed to extract keys for %d-server: %v", i, err)
}

keys = tc.config.Keys
serverMaterial := append([]byte{}, keys.RemoteMasterKey...)
serverMaterial = append(serverMaterial, keys.LocalMasterKey...)
serverMaterial = append(serverMaterial, keys.RemoteMasterSalt...)
serverMaterial = append(serverMaterial, keys.LocalMasterSalt...)

if !bytes.Equal(serverMaterial, m.exported) {
t.Errorf("material reconstruction failed for %d-server:\n%#v\nexpected\n%#v", i, serverMaterial, m.exported)
}

}
}
24 changes: 17 additions & 7 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ type session struct {
nextConn net.Conn
}

// Config is used to configure a session.
// You can provide either a KeyingMaterialExporter to export keys
// or directly pass the keys themselves.
// After a Config is passed to a session it must not be modified.
type Config struct {
Keys SessionKeys
Profile ProtectionProfile
}

// SessionKeys bundles the keys required to setup an SRTP session
type SessionKeys struct {
LocalMasterKey []byte
LocalMasterSalt []byte
RemoteMasterKey []byte
RemoteMasterSalt []byte
}

func (s *session) getOrCreateReadStream(ssrc uint32, child streamSession, proto readStream) (readStream, bool) {
s.readStreamsLock.Lock()
defer s.readStreamsLock.Unlock()
Expand All @@ -48,13 +65,6 @@ func (s *session) getOrCreateReadStream(ssrc uint32, child streamSession, proto
return r, false
}

func (s *session) initalize() {
s.readStreams = map[uint32]readStream{}
s.newStream = make(chan readStream)
s.started = make(chan interface{})
s.closed = make(chan interface{})
}

func (s *session) close() error {
if s.nextConn == nil {
return nil
Expand Down
31 changes: 21 additions & 10 deletions session_srtcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@ type SessionSRTCP struct {
writeStream *WriteStreamSRTCP
}

// CreateSessionSRTCP creates a new SessionSRTCP
func CreateSessionSRTCP() *SessionSRTCP {
s := &SessionSRTCP{}
// NewSessionSRTCP creates a SRTCP session using conn as the underlying transport.
func NewSessionSRTCP(conn net.Conn, config *Config) (*SessionSRTCP, error) {
s := &SessionSRTCP{
session: session{
nextConn: conn,
readStreams: map[uint32]readStream{},
newStream: make(chan readStream),
started: make(chan interface{}),
closed: make(chan interface{}),
},
}
s.writeStream = &WriteStreamSRTCP{s}
s.session.initalize()
return s
}

// Start initializes any crypto context and allows reading/writing to begin
func (s *SessionSRTCP) Start(localMasterKey, localMasterSalt, remoteMasterKey, remoteMasterSalt []byte, profile ProtectionProfile, nextConn net.Conn) error {
s.session.nextConn = nextConn
return s.session.start(localMasterKey, localMasterSalt, remoteMasterKey, remoteMasterSalt, profile, s)
err := s.session.start(
config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt,
config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt,
config.Profile,
s,
)
if err != nil {
return nil, err
}
return s, nil
}

// OpenWriteStream returns the global write stream for the Session
Expand Down
27 changes: 22 additions & 5 deletions session_srtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,29 @@ type SessionSRTP struct {
writeStream *WriteStreamSRTP
}

// CreateSessionSRTP creates a new SessionSRTP
func CreateSessionSRTP() *SessionSRTP {
s := &SessionSRTP{}
// NewSessionSRTP creates a SRTP session using conn as the underlying transport.
func NewSessionSRTP(conn net.Conn, config *Config) (*SessionSRTP, error) {
s := &SessionSRTP{
session: session{
nextConn: conn,
readStreams: map[uint32]readStream{},
newStream: make(chan readStream),
started: make(chan interface{}),
closed: make(chan interface{}),
},
}
s.writeStream = &WriteStreamSRTP{s}
s.session.initalize()
return s

err := s.session.start(
config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt,
config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt,
config.Profile,
s,
)
if err != nil {
return nil, err
}
return s, nil
}

// Start initializes any crypto context and allows reading/writing to begin
Expand Down

0 comments on commit 93a8f4f

Please sign in to comment.