Files
gortsplib/wrapped_srtp_context.go
Alessandro Ros b6f80fd5f2 fix SP payload of MIKEY messages (#827) (#935)
fill PayloadSPPolicyParamTypeAuthTagLen and
PayloadSPPolicyParamTypeSessionAuthKeyLen properly.
2025-11-02 17:04:51 +01:00

276 lines
6.7 KiB
Go

package gortsplib
import (
"bytes"
"crypto/rand"
"fmt"
"sync"
"time"
"github.com/bluenviron/gortsplib/v5/pkg/mikey"
"github.com/bluenviron/gortsplib/v5/pkg/ntp"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/pion/srtp/v3"
)
func mikeyGetPayload[T mikey.Payload](mikeyMsg *mikey.Message) (T, bool) {
var zero T
for _, wrapped := range mikeyMsg.Payloads {
if val, ok := wrapped.(T); ok {
return val, true
}
}
return zero, false
}
func mikeyGetSPPolicy(spPayload *mikey.PayloadSP, typ mikey.PayloadSPPolicyParamType) ([]byte, bool) {
for _, pl := range spPayload.PolicyParams {
if pl.Type == typ {
return pl.Value, true
}
}
return nil, false
}
func mikeyToContext(mikeyMsg *mikey.Message) (*wrappedSRTPContext, error) {
timePayload, ok := mikeyGetPayload[*mikey.PayloadT](mikeyMsg)
if !ok {
return nil, fmt.Errorf("time payload not present")
}
ts := ntp.Decode(timePayload.TSValue)
diff := time.Since(ts)
if diff < -time.Hour || diff > time.Hour {
return nil, fmt.Errorf("NTP difference is too high")
}
spPayload, ok := mikeyGetPayload[*mikey.PayloadSP](mikeyMsg)
if !ok {
return nil, fmt.Errorf("SP payload not present")
}
v, ok := mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeEncrAlg)
if !ok || !bytes.Equal(v, []byte{1}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeEncrAlg")
}
v, ok = mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeSessionEncrKeyLen)
if !ok || !bytes.Equal(v, []byte{16}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeSessionEncrKeyLen")
}
v, ok = mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeAuthAlg)
if !ok || !bytes.Equal(v, []byte{1}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeAuthAlg")
}
v, ok = mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeSRTPEncrOffOn)
if !ok || !bytes.Equal(v, []byte{1}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeSRTPEncrOffOn")
}
v, ok = mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeSRTCPEncrOffOn)
if !ok || !bytes.Equal(v, []byte{1}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeSRTCPEncrOffOn")
}
v, ok = mikeyGetSPPolicy(spPayload, mikey.PayloadSPPolicyParamTypeSRTPAuthOffOn)
if !ok || !bytes.Equal(v, []byte{1}) {
return nil, fmt.Errorf("missing or unsupported policy: PayloadSPPolicyParamTypeSRTPAuthOffOn")
}
kemacPayload, ok := mikeyGetPayload[*mikey.PayloadKEMAC](mikeyMsg)
if !ok {
return nil, fmt.Errorf("KEMAC payload not present")
}
if len(kemacPayload.SubPayloads) != 1 {
return nil, fmt.Errorf("multiple keys are present")
}
if len(kemacPayload.SubPayloads[0].KeyData) != srtpKeyLength {
return nil, fmt.Errorf("unexpected key size: %d", len(kemacPayload.SubPayloads[0].KeyData))
}
ssrcs := make([]uint32, len(mikeyMsg.Header.CSIDMapInfo))
startROCs := make([]uint32, len(mikeyMsg.Header.CSIDMapInfo))
for i, entry := range mikeyMsg.Header.CSIDMapInfo {
ssrcs[i] = entry.SSRC
startROCs[i] = entry.ROC
}
srtpCtx := &wrappedSRTPContext{
key: kemacPayload.SubPayloads[0].KeyData,
ssrcs: ssrcs,
startROCs: startROCs,
}
err := srtpCtx.initialize()
if err != nil {
return nil, err
}
return srtpCtx, nil
}
func mikeyGenerate(ctx *wrappedSRTPContext) (*mikey.Message, error) {
csbID, err := randUint32()
if err != nil {
return nil, err
}
msg := &mikey.Message{
Header: mikey.Header{
Version: 1,
CSBID: csbID,
},
}
msg.Header.CSIDMapInfo = make([]mikey.SRTPIDEntry, len(ctx.ssrcs))
n := 0
for _, ssrc := range ctx.ssrcs {
msg.Header.CSIDMapInfo[n] = mikey.SRTPIDEntry{
PolicyNo: 0,
SSRC: ssrc,
ROC: ctx.roc(ssrc),
}
n++
}
randData := make([]byte, 16)
_, err = rand.Read(randData)
if err != nil {
return nil, err
}
keyLen, err := ctx.profile.KeyLen()
if err != nil {
return nil, err
}
authKeyLen, err := ctx.profile.AuthKeyLen()
if err != nil {
return nil, err
}
authTagLen, err := ctx.profile.AuthTagRTPLen()
if err != nil {
return nil, err
}
msg.Payloads = []mikey.Payload{
&mikey.PayloadT{
TSType: 0,
TSValue: ntp.Encode(time.Now()),
},
&mikey.PayloadRAND{
Data: randData,
},
&mikey.PayloadSP{
PolicyParams: []mikey.PayloadSPPolicyParam{
{
Type: mikey.PayloadSPPolicyParamTypeEncrAlg,
Value: []byte{1},
},
{
Type: mikey.PayloadSPPolicyParamTypeSessionEncrKeyLen,
Value: []byte{byte(keyLen)},
},
{
Type: mikey.PayloadSPPolicyParamTypeAuthAlg,
Value: []byte{1},
},
{
Type: mikey.PayloadSPPolicyParamTypeSessionAuthKeyLen,
Value: []byte{byte(authKeyLen)},
},
{
Type: mikey.PayloadSPPolicyParamTypeSRTPEncrOffOn,
Value: []byte{1},
},
{
Type: mikey.PayloadSPPolicyParamTypeSRTCPEncrOffOn,
Value: []byte{1},
},
{
Type: mikey.PayloadSPPolicyParamTypeSRTPAuthOffOn,
Value: []byte{1},
},
{
Type: mikey.PayloadSPPolicyParamTypeAuthTagLen,
Value: []byte{byte(authTagLen)},
},
},
},
&mikey.PayloadKEMAC{
SubPayloads: []*mikey.SubPayloadKeyData{
{
Type: mikey.SubPayloadKeyDataKeyTypeTEK,
KeyData: ctx.key,
},
},
},
}
return msg, nil
}
// srtp.Context with
// - accessible key
// - accessible SSRCs
// - mutex around Encrypt*, ROC*
type wrappedSRTPContext struct {
key []byte
ssrcs []uint32
startROCs []uint32
profile srtp.ProtectionProfile
w *srtp.Context
mutex sync.RWMutex
}
func (ctx *wrappedSRTPContext) initialize() error {
ctx.profile = srtp.ProtectionProfileAes128CmHmacSha1_80
var err error
ctx.w, err = srtp.CreateContext(ctx.key[:16], ctx.key[16:], ctx.profile)
if err != nil {
return err
}
for i, roc := range ctx.startROCs {
ctx.w.SetROC(ctx.ssrcs[i], roc)
}
return nil
}
func (ctx *wrappedSRTPContext) decryptRTP(dst []byte, encrypted []byte, header *rtp.Header) ([]byte, error) {
return ctx.w.DecryptRTP(dst, encrypted, header)
}
func (ctx *wrappedSRTPContext) decryptRTCP(dst []byte, encrypted []byte, header *rtcp.Header) ([]byte, error) {
return ctx.w.DecryptRTCP(dst, encrypted, header)
}
func (ctx *wrappedSRTPContext) encryptRTP(dst []byte, plaintext []byte, header *rtp.Header) ([]byte, error) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
return ctx.w.EncryptRTP(dst, plaintext, header)
}
func (ctx *wrappedSRTPContext) encryptRTCP(dst []byte, decrypted []byte, header *rtcp.Header) ([]byte, error) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
return ctx.w.EncryptRTCP(dst, decrypted, header)
}
func (ctx *wrappedSRTPContext) roc(ssrc uint32) uint32 {
ctx.mutex.RLock()
defer ctx.mutex.RUnlock()
v, _ := ctx.w.ROC(ssrc)
return v
}