From cf8b1e2b168bb666cec37695923fadfc4d27eb14 Mon Sep 17 00:00:00 2001 From: Sergey Kheyfets Date: Mon, 9 Dec 2024 12:45:26 -0500 Subject: [PATCH] Add audio encryption/decryption utilities --- encryption.go | 204 ++++++++++++++++++++++++++++++++++++++++++++ encryption_test.go | 80 +++++++++++++++++ go.mod | 16 ++-- go.sum | 38 +++++---- localparticipant.go | 1 + publication.go | 2 + room.go | 12 +++ 7 files changed, 327 insertions(+), 26 deletions(-) create mode 100644 encryption.go create mode 100644 encryption_test.go diff --git a/encryption.go b/encryption.go new file mode 100644 index 00000000..103db07a --- /dev/null +++ b/encryption.go @@ -0,0 +1,204 @@ +package lksdk + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "errors" + "io" + + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/pbkdf2" +) + +const ( + LIVEKIT_SDK_SALT = "LKFrameEncryptionKey" + LIVEKIT_IV_LENGTH = 12 + LIVEKIT_PBKDF_ITERATIONS = 100000 + LIVEKIT_KEY_SIZE_BYTES = 16 + LIVEKIT_HKDF_INFO_BYTES = 128 + unencrypted_audio_bytes = 1 +) + +var ErrIncorrectKeyLength = errors.New("incorrect key length for encryption/decryption") +var ErrUnableGenerateIV = errors.New("unable to generate iv for encryption") +var ErrIncorrectIVLength = errors.New("incorrect iv length") +var ErrIncorrectSecretLength = errors.New("input secret provided to derivation function cannot be empty or nil") +var ErrIncorrectSaltLength = errors.New("input salt provided to derivation function cannot be empty or nil") + +func DeriveKeyFromString(password string) ([]byte, error) { + return DeriveKeyFromStringCustomSalt(password, LIVEKIT_SDK_SALT) +} + +func DeriveKeyFromStringCustomSalt(password, salt string) ([]byte, error) { + + if password == "" { + return nil, ErrIncorrectSecretLength + } + if salt == "" { + return nil, ErrIncorrectSaltLength + } + + encPassword := []byte(password) + encSalt := []byte(salt) + + return pbkdf2.Key(encPassword, encSalt, LIVEKIT_PBKDF_ITERATIONS, LIVEKIT_KEY_SIZE_BYTES, sha256.New), nil + +} + +func DeriveKeyFromBytes(secret []byte) ([]byte, error) { + return DeriveKeyFromBytesCustomSalt(secret, LIVEKIT_SDK_SALT) +} + +func DeriveKeyFromBytesCustomSalt(secret []byte, salt string) ([]byte, error) { + + info := make([]byte, LIVEKIT_HKDF_INFO_BYTES) + encSalt := []byte(salt) + + if secret == nil { + return nil, ErrIncorrectSecretLength + } + if salt == "" { + return nil, ErrIncorrectSaltLength + } + + hkdfReader := hkdf.New(sha256.New, secret, encSalt, info) + + key := make([]byte, LIVEKIT_KEY_SIZE_BYTES) + _, err := io.ReadFull(hkdfReader, key) + if err != nil { + return nil, err + } + + return key, nil + +} + +// Take audio sample (body of RTP) encrypted by LiveKit client SDK, extract IV and decrypt using provided key +// Encrypted sample format based on livekit client sdk +// ---------+-------------------------+---------+---- +// payload |IV...(length = IV_LENGTH)|IV_LENGTH|KID| +// ---------+-------------------------+---------+---- +// First byte of audio frame is not encrypted and only authenticated +// payload - variable bytes +// IV - variable bytes (equal to IV_LENGTH bytes) +// IV_LENGTH - 1 byte +// KID (Key ID) - 1 byte - ignored here, key is provided as parameter to function +func DecryptGCMAudioSample(sample, key, sifTrailer []byte) ([]byte, error) { + + if len(key) != 16 { + return nil, ErrIncorrectKeyLength + } + + if sifTrailer != nil && len(sample) >= len(sifTrailer) { + possibleTrailer := sample[len(sample)-len(sifTrailer):] + if bytes.Equal(possibleTrailer, sifTrailer) { + // this is unencrypted Server Injected Frame (SIF) thas should be dropped + return nil, nil + } + + } + + // variable naming is kept close to LiveKit client SDK decrypt function + // https://github.com/livekit/client-sdk-js/blob/main/src/e2ee/worker/FrameCryptor.ts#L402 + + frameHeader := sample[:unencrypted_audio_bytes] // first unencrypted bytes are "frameHeader" and used for authentication later + frameTrailer := sample[len(sample)-2:] // last 2 bytes having IV_LENGTH and KID (1 byte each) + ivLength := int(frameTrailer[0]) // single byte, Endianness doesn't matter + ivStart := len(sample) - len(frameTrailer) - ivLength + if ivStart < 0 { + return nil, ErrIncorrectIVLength + } + + iv := make([]byte, ivLength) + copy(iv, sample[ivStart:ivStart+ivLength]) // copy IV value out of sample into iv + + cipherTextStart := len(frameHeader) + cipherTextLength := len(sample) - len(frameTrailer) - ivLength - len(frameHeader) + cipherText := make([]byte, cipherTextLength) + copy(cipherText, sample[cipherTextStart:cipherTextStart+cipherTextLength]) + + // setup AES + aesCipher, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aesGCM, err := cipher.NewGCMWithNonceSize(aesCipher, ivLength) // standard Nonce size is 12 bytes, but since it MAY be different in the sample, we use the one from the sample + if err != nil { + return nil, err + } + + // fmt.Println("**** DECRYPTION BEGIN ********") + plainText, err := aesGCM.Open(nil, iv, cipherText, frameHeader) + if err != nil { + return nil, err + } + + newData := make([]byte, len(frameHeader)+len(plainText)) // allocate space for final packet + + _ = copy(newData[0:], frameHeader) // put unencrypted frameHeader first + _ = copy(newData[len(frameHeader):], plainText) // add decrypted remaining value + + return newData, nil + +} + +// Take audio sample (body of RTP) and encrypts it using AES-GCM 128bit with provided key +// Encrypted sample format based on livekit client sdk +// ---------+-------------------------+---------+---- +// payload |IV...(length = IV_LENGTH)|IV_LENGTH|KID| +// ---------+-------------------------+---------+---- +// First byte of audio frame is not encrypted and only authenticated +// payload - variable bytes +// IV - variable bytes (equal to IV_LENGTH bytes) - 12 random bytes +// IV_LENGTH - 1 byte - 12 bytes fixed +// KID (Key ID) - 1 byte - taken from "kid" parameter +func EncryptGCMAudioSample(sample, key []byte, kid uint8) ([]byte, error) { + + if len(key) != 16 { + return nil, ErrIncorrectKeyLength + } + + // variable naming is kept close to LiveKit client SDK decrypt function + // https://github.com/livekit/client-sdk-js/blob/main/src/e2ee/worker/FrameCryptor.ts#L402 + + frameHeader := append(make([]byte, 0), sample[:unencrypted_audio_bytes]...) // first unencrypted bytes are "frameHeader" and used for authentication later + iv := make([]byte, LIVEKIT_IV_LENGTH) + _, err := rand.Read(iv) + if err != nil { + return nil, errors.Join(ErrUnableGenerateIV, err) + } + + frameTrailer := []byte{LIVEKIT_IV_LENGTH, kid} // last 2 bytes having IV_LENGTH and KID (1 byte each) + + plainTextStart := len(frameHeader) + plainTextLength := len(sample) - len(frameHeader) + plainText := make([]byte, plainTextLength) + copy(plainText, sample[plainTextStart:plainTextStart+plainTextLength]) + + // setup AES + aesCipher, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aesGCM, err := cipher.NewGCMWithNonceSize(aesCipher, LIVEKIT_IV_LENGTH) // standard Nonce size is 12 bytes, but using one from defined constant (which matches Javascript SDK) + if err != nil { + return nil, err + } + + cipherText := aesGCM.Seal(nil, iv, plainText, frameHeader) + + newData := make([]byte, len(frameHeader)+len(cipherText)+len(iv)+len(frameTrailer)) // allocate space for final packet + + _ = copy(newData[0:], frameHeader) // put unencrypted frameHeader first + _ = copy(newData[len(frameHeader):], cipherText) // add cipherText + _ = copy(newData[len(frameHeader)+len(cipherText):], iv) // add iv + _ = copy(newData[len(frameHeader)+len(cipherText)+len(iv):], frameTrailer) // add trailer + + return newData, nil + +} diff --git a/encryption_test.go b/encryption_test.go new file mode 100644 index 00000000..f9c3add3 --- /dev/null +++ b/encryption_test.go @@ -0,0 +1,80 @@ +package lksdk + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var opusEncryptedFrame = []byte{120, 145, 24, 159, 76, 65, 130, 48, 144, 249, 17, 112, 134, 78, 250, 129, 171, 194, 16, 173, 73, 196, 5, 152, 69, 225, 28, 210, 196, 241, 226, 139, 231, 172, 51, 38, 139, 179, 245, 182, 170, 8, 122, 117, 98, 144, 123, 95, 73, 89, 119, 39, 205, 20, 191, 55, 121, 59, 239, 192, 85, 224, 228, 143, 10, 113, 195, 223, 118, 42, 2, 32, 22, 17, 77, 227, 109, 160, 245, 202, 189, 63, 162, 164, 5, 241, 24, 151, 45, 42, 165, 131, 171, 243, 141, 53, 35, 131, 141, 52, 253, 188, 12, 0} +var opusDecryptedFrame = []byte{120, 11, 109, 82, 113, 132, 189, 156, 220, 173, 30, 109, 87, 54, 173, 99, 26, 126, 166, 37, 127, 234, 110, 211, 230, 152, 181, 235, 197, 19, 140, 230, 179, 35, 131, 132, 29, 192, 97, 247, 108, 53, 183, 214, 77, 181, 173, 206, 175, 7, 228, 145, 93, 155, 155, 142, 14, 27, 111, 64, 96, 196, 229, 189, 142, 59, 149, 169, 99, 225, 216, 85, 186, 182} +var opusSilenceFrame = []byte{0xf8, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +var sifTrailer = []byte{50, 86, 10, 220, 108, 185, 57, 211} +var testPassphrase = "12345" + +func TestDeriveKeyFromString(t *testing.T) { + + password := "12345" + + key, err := DeriveKeyFromString(password) + expectedKey := []byte{15, 94, 198, 66, 93, 211, 116, 46, 55, 97, 232, 121, 189, 233, 224, 22} + + assert.Nil(t, err) + assert.Equal(t, key, expectedKey) +} + +func TestDeriveKeyFromBytes(t *testing.T) { + + inputSecret := []byte{34, 21, 187, 202, 134, 204, 168, 62, 5, 105, 40, 244, 88} + expectedKey := []byte{129, 224, 93, 62, 17, 203, 99, 136, 101, 35, 149, 128, 189, 152, 251, 76} + + key, err := DeriveKeyFromBytes(inputSecret) + assert.Nil(t, err) + assert.Equal(t, expectedKey, key) + +} + +func TestDecryptAudioSample(t *testing.T) { + + key, err := DeriveKeyFromString(testPassphrase) + assert.Nil(t, err) + + decryptedFrame, err := DecryptGCMAudioSample(opusEncryptedFrame, key, sifTrailer) + + assert.Nil(t, err) + assert.Equal(t, opusDecryptedFrame, decryptedFrame) + + var sifFrame []byte + sifFrame = append(sifFrame, opusSilenceFrame...) + sifFrame = append(sifFrame, sifTrailer...) + + decryptedFrame, err = DecryptGCMAudioSample(sifFrame, key, sifTrailer) + assert.Nil(t, err) + assert.Nil(t, decryptedFrame) + +} + +func TestEncryptAudioSample(t *testing.T) { + + key, err := DeriveKeyFromString(testPassphrase) + assert.Nil(t, err) + + encryptedFrame, err := EncryptGCMAudioSample(opusDecryptedFrame, key, 0) + + assert.Nil(t, err) + + // IV is generated randomly so to verify we decrypt and make sure that we got the expected plain text frame + decryptedFrame, err := DecryptGCMAudioSample(encryptedFrame, key, sifTrailer) + assert.Nil(t, err) + assert.Equal(t, opusDecryptedFrame, decryptedFrame) + +} diff --git a/go.mod b/go.mod index f17389d5..ec8439ac 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/twitchtv/twirp v8.1.3+incompatible go.uber.org/atomic v1.11.0 + golang.org/x/crypto v0.30.0 golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f google.golang.org/protobuf v1.35.2 ) @@ -66,13 +67,12 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect - google.golang.org/grpc v1.65.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.68.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6a45fde6..771382b0 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/frostbyte73/core v0.0.12 h1:kySA8+Os6eqnPFoExD2T7cehjSAY1MRyIViL0yTy2uc= github.com/frostbyte73/core v0.0.12/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -37,6 +37,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -168,8 +170,8 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -182,13 +184,13 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -201,8 +203,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -218,20 +220,20 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f h1:RARaIm8pxYuxyNPbBQf5igT7XdOyCNtat1qAT2ZxjU4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/localparticipant.go b/localparticipant.go index 66cf6647..6d3200b9 100644 --- a/localparticipant.go +++ b/localparticipant.go @@ -77,6 +77,7 @@ func (p *LocalParticipant) PublishTrack(track webrtc.TrackLocal, opts *TrackPubl DisableDtx: opts.DisableDTX, Stereo: opts.Stereo, Stream: opts.Stream, + Encryption: opts.Encryption, } if kind == TrackKindVideo { // single layer diff --git a/publication.go b/publication.go index 6ca1c087..50e232b7 100644 --- a/publication.go +++ b/publication.go @@ -416,4 +416,6 @@ type TrackPublicationOptions struct { // which stream the track belongs to, used to group tracks together. // if not specified, server will infer it from track source to bundle camera/microphone, screenshare/audio together Stream string + // encryption type + Encryption livekit.Encryption_Type } diff --git a/room.go b/room.go index 2556274d..f66308a1 100644 --- a/room.go +++ b/room.go @@ -165,6 +165,8 @@ type Room struct { serverInfo *livekit.ServerInfo regionURLProvider *regionURLProvider + sifTrailer []byte + lock sync.RWMutex } @@ -335,6 +337,8 @@ func (r *Room) JoinWithToken(url, token string, opts ...ConnectOption) error { r.metadata = joinRes.Room.Metadata r.serverInfo = joinRes.ServerInfo r.connectionState = ConnectionStateConnected + r.sifTrailer = make([]byte, len(joinRes.SifTrailer)) + copy(r.sifTrailer, joinRes.SifTrailer) r.lock.Unlock() r.setSid(joinRes.Room.Sid, false) @@ -464,6 +468,14 @@ func (r *Room) ServerInfo() *livekit.ServerInfo { return proto.Clone(r.serverInfo).(*livekit.ServerInfo) } +func (r *Room) SifTrailer() []byte { + r.lock.RLock() + defer r.lock.RUnlock() + trailer := make([]byte, len(r.sifTrailer)) + copy(trailer, r.sifTrailer) + return trailer +} + func (r *Room) addRemoteParticipant(pi *livekit.ParticipantInfo, updateExisting bool) *RemoteParticipant { r.lock.Lock() defer r.lock.Unlock()