Skip to content

Add H.264 video E2EE and data channel encryption for Go publishers #870

@samthesloth

Description

@samthesloth

Summary

The Go SDK currently supports audio sample E2EE (EncryptGCMAudioSample from #573/#583) and PCM track encryption (#674), but there's no equivalent for H.264 video frames or data channel messages. This means Go publishers (screen sharing, server-side compositing, IoT cameras) can't participate in E2EE rooms.

Use Case

We're building a remote desktop system where a Go runner captures the screen via FFmpeg, encodes to H.264, and publishes via the Go SDK. The JS viewer subscribes using the standard built-in roomOpts.encryption + ExternalE2EEKeyProvider + e2ee-worker.js. We need the Go publisher to produce encrypted frames that the JS FrameCryptor can decrypt.

What We Built (fork)

We have a working implementation on a fork that adds:

1. H.264 video frame encryption (encryption.go)

EncryptGCMH264Sample / EncryptGCMH264SampleCustomCipher which match the JS FrameCryptor format exactly:

  • Parses H.264 NALUs via findNALUIndices to find the first slice (IDR/non-IDR)
  • Leaves everything before the slice + 2 bytes unencrypted (used as AES-GCM AAD)
  • Samples with no slice NALUs (SPS/PPS only) pass through unmodified
  • Encrypts payload with AES-128-GCM (random 12-byte IV)
  • Applies RBSP escaping (writeRBSP) to prevent accidental H.264 start codes
  • Frame format: [header][ciphertext+tag][IV][IV_LENGTH][KID]

Corresponding DecryptGCMH264Sample for Go-side decryption.

Uses the existing DeriveKeyFromString (PBKDF2, salt "LKFrameEncryptionKey", 100k iterations) so the same shared passphrase works on both sides.

2. Data channel encryption (data_cryptor.go, e2ee.go)

  • DataCryptor encrypts outgoing DataPacket values into EncryptedPacket protobuf wrappers
  • Decrypts incoming EncryptedPacket messages back to their original types
  • Supports: user messages, chat, RPC request/response/ack, stream headers/chunks/trailers
  • ExternalKeyProvider with SetKeyFromPassphrase / SetRawKey and key index support
  • room.SetEncryption(&EncryptionOptions{KeyProvider: kp}) — integration point

3. Room/engine integration (room.go, engine.go)

  • Room.SetEncryption() wires up the DataCryptor on the engine
  • engine.publishDataPacket() encrypts outgoing data
  • engine.handleDataPacket() decrypts incoming EncryptedPacket

Compatibility

Tested against the JS SDK's livekit-client.e2ee.worker.mjs:

  • JS subscriber decrypts Go-published H.264 frames via FrameCryptor
  • JS sends encrypted data (input commands, RPC) -> Go decrypts
  • Go sends encrypted data (monitor list, RPC responses) -> JS decrypts
  • Shared passphrase derivation is identical on both sides

Scope

File Change Lines
encryption.go H.264 encrypt/decrypt + NALU helpers + RBSP ~246 added
e2ee.go EncryptionOptions, KeyProvider interface, ExternalKeyProvider new file
data_cryptor.go DataCryptor for data packet encrypt/decrypt new file
engine.go Wire DataCryptor into publish/receive paths ~32 added
room.go SetEncryption() method ~8 added

Happy to open a PR if there's interest. Would also like guidance on:

  • Whether H.265 NALU support should be included (we only implemented H.264)
  • Preferred API surface (we followed the EncryptGCMAudioSample pattern)
  • Whether DataCryptor should be opt-in per room.SetEncryption() or automatic when keys are set

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions