Skip to content

Commit

Permalink
Support per-channel encryption modes with available fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
BrandtHill committed Sep 20, 2024
1 parent 2c2cbee commit f4246ce
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 24 deletions.
12 changes: 6 additions & 6 deletions guides/functionality/voice.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ hardware architecture your bot is running on. If you're interested, keep reading

#### Encryption Mode Configuration Options

This is a compile-time configuration option, so should you wish to set it,
do it in `config.exs` or one of its imported config files, *not* `runtime.exs`.
This is a runtime configuration option. Some Discord voice servers may not support your
configured encryption mode, and in these cases a fallback mode will be selected.

```elixir
config :nostrum, :voice_encryption_mode, :aes256_gcm # Default
Expand All @@ -199,10 +199,10 @@ Available configuration options are as follows:
- `:xsalsa20_poly1305`
- `:xsalsa20_poly1305_suffix`
- `:xsalsa20_poly1305_lite`
- `:xsalsa20_poly1305_lite_rtpsize` *(not yet documented by Discord)*
- `:aead_xchacha20_poly1305_rtpsize` *(not yet documented by Discord)*
- `:aead_aes256_gcm` *(not yet documented by Discord)*
- `:aead_aes256_gcm_rtpsize` *(not yet documented by Discord)*
- `:xsalsa20_poly1305_lite_rtpsize`
- `:aead_xchacha20_poly1305_rtpsize`
- `:aead_aes256_gcm`
- `:aead_aes256_gcm_rtpsize`
- `:xchacha20_poly1305` (alias for `:aead_xchacha20_poly1305_rtpsize`)
- `:aes256_gcm` (alias for `:aead_aes256_gcm_rtpsize`)

Expand Down
1 change: 1 addition & 0 deletions lib/nostrum/struct/voice_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule Nostrum.Struct.VoiceState do
:ip,
:port,
:udp_socket,
:encryption_mode,
:rtp_sequence,
:rtp_timestamp,
:ffmpeg_proc,
Expand Down
5 changes: 5 additions & 0 deletions lib/nostrum/struct/voice_ws_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule Nostrum.Struct.VoiceWSState do
:gateway,
:identified,
:seq,
:encryption_mode,
:last_heartbeat_send,
:last_heartbeat_ack,
:heartbeat_ack,
Expand Down Expand Up @@ -67,6 +68,9 @@ defmodule Nostrum.Struct.VoiceWSState do
@typedoc "Sequence number for buffering server-sent events"
@type seq :: integer()

@typedoc "Encryption mode selected for voice channel"
@type encryption_mode :: Nostrum.Voice.Crypto.cipher()

@typedoc """
The time the last heartbeat was sent, if a heartbeat hasn't been sent it
will be the time the websocket process was started
Expand Down Expand Up @@ -101,6 +105,7 @@ defmodule Nostrum.Struct.VoiceWSState do
gateway: gateway,
identified: identified,
seq: seq,
encryption_mode: encryption_mode,
last_heartbeat_send: last_heartbeat_send,
last_heartbeat_ack: last_heartbeat_ack,
heartbeat_ack: heartbeat_ack,
Expand Down
37 changes: 22 additions & 15 deletions lib/nostrum/voice/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,34 @@ defmodule Nostrum.Voice.Crypto do

@type cipher :: cipher_non_rtpsize() | cipher_alias() | cipher_rtpsize()

@mode Application.compile_env(:nostrum, :voice_encryption_mode, :aes256_gcm)
@fallback_mode :aead_xchacha20_poly1305_rtpsize

@mode_string Map.get(
%{
xchacha20_poly1305: "aead_xchacha20_poly1305_rtpsize",
aes256_gcm: "aead_aes256_gcm_rtpsize"
},
@mode,
"#{@mode}"
)
@mode_aliases %{
xchacha20_poly1305: :aead_xchacha20_poly1305_rtpsize,
aes256_gcm: :aead_aes256_gcm_rtpsize
}

def encryption_mode, do: @mode_string
@spec encryption_mode(list(String.t())) :: cipher()
def encryption_mode(available_modes) do
mode = Application.get_env(:nostrum, :voice_encryption_mode, :aes256_gcm)

def encrypt(voice, data) do
mode = Map.get(@mode_aliases, mode, mode)

if "#{mode}" in available_modes do
mode
else
@fallback_mode
end
end

def encrypt(%VoiceState{encryption_mode: mode} = voice, data) do
header = Audio.rtp_header(voice)
unquote(:"encrypt_#{@mode}")(voice, data, header)
apply(__MODULE__, :"encrypt_#{mode}", [voice, data, header])
end

def decrypt(%VoiceState{secret_key: key}, data), do: decrypt(key, data)
def decrypt(%VoiceWSState{secret_key: key}, data), do: decrypt(key, data)
def decrypt(key, data), do: unquote(:"decrypt_#{@mode}")(key, data)
def decrypt(%{secret_key: key, encryption_mode: mode}, data) do
apply(__MODULE__, :"decrypt_#{mode}", [key, data])
end

def encrypt_xsalsa20_poly1305(%VoiceState{secret_key: key}, data, header) do
nonce = header <> <<0::unit(8)-size(12)>>
Expand Down
6 changes: 5 additions & 1 deletion lib/nostrum/voice/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Nostrum.Voice.Event do
alias Nostrum.Struct.VoiceWSState
alias Nostrum.Voice
alias Nostrum.Voice.Audio
alias Nostrum.Voice.Crypto
alias Nostrum.Voice.Payload
alias Nostrum.Voice.Session

Expand All @@ -27,17 +28,20 @@ defmodule Nostrum.Voice.Event do
defp handle_event(:ready, payload, state) do
Logger.debug("VOICE READY")

mode = Crypto.encryption_mode(payload["d"]["modes"])

voice =
Voice.update_voice(state.guild_id,
ssrc: payload["d"]["ssrc"],
ip: payload["d"]["ip"],
port: payload["d"]["port"],
encryption_mode: mode,
udp_socket: Audio.open_udp()
)

{my_ip, my_port} = Audio.discover_ip(voice.udp_socket, voice.ip, voice.port, voice.ssrc)

{state, Payload.select_protocol_payload(my_ip, my_port)}
{%{state | encryption_mode: mode}, Payload.select_protocol_payload(my_ip, my_port, mode)}
end

defp handle_event(:session_description, payload, state) do
Expand Down
4 changes: 2 additions & 2 deletions lib/nostrum/voice/payload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ defmodule Nostrum.Voice.Payload do
|> build_payload("RESUME")
end

def select_protocol_payload(ip, port) do
def select_protocol_payload(ip, port, mode) do
%{
protocol: "udp",
data: %{
address: ip,
port: port,
mode: Crypto.encryption_mode()
mode: "#{mode}"
}
}
|> build_payload("SELECT_PROTOCOL")
Expand Down

0 comments on commit f4246ce

Please sign in to comment.