From 788fb37e1ec7e6ee31e57510939ee63ca8174d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Fri, 24 Nov 2023 20:53:30 +0100 Subject: [PATCH] Verify peer cert fingerprint --- lib/ex_webrtc/dtls_transport.ex | 48 ++++++++----- lib/ex_webrtc/peer_connection.ex | 35 ++++++++-- lib/ex_webrtc/sdp_utils.ex | 73 ++++++++++++++++++++ mix.exs | 2 +- mix.lock | 14 ++-- test/ex_webrtc/dtls_transport_test.exs | 38 +++++++---- test/ex_webrtc/peer_connection_test.exs | 89 +++++++++++++++++++++---- 7 files changed, 243 insertions(+), 56 deletions(-) diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 34b83d8f..4b80f1d2 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -8,6 +8,7 @@ defmodule ExWebRTC.DTLSTransport do require Logger alias ExICE.ICEAgent + alias ExWebRTC.Utils @type dtls_transport() :: GenServer.server() @@ -48,9 +49,10 @@ defmodule ExWebRTC.DTLSTransport do end @doc false - @spec start_dtls(dtls_transport(), :active | :passive) :: :ok | {:error, :already_started} - def start_dtls(dtls_transport, mode) do - GenServer.call(dtls_transport, {:start_dtls, mode}) + @spec start_dtls(dtls_transport(), :active | :passive, binary()) :: + :ok | {:error, :already_started} + def start_dtls(dtls_transport, mode, peer_fingerprint) do + GenServer.call(dtls_transport, {:start_dtls, mode, peer_fingerprint}) end @doc false @@ -61,11 +63,8 @@ defmodule ExWebRTC.DTLSTransport do @impl true def init([ice_config, ice_module, owner]) do - # temporary hack to generate certs - dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) - cert = ExDTLS.get_cert(dtls) - pkey = ExDTLS.get_pkey(dtls) - fingerprint = ExDTLS.get_cert_fingerprint(dtls) + {pkey, cert} = ExDTLS.generate_key_cert() + fingerprint = ExDTLS.get_cert_fingerprint(cert) {:ok, ice_agent} = ice_module.start_link(:controlled, ice_config) srtp = ExLibSRTP.new() @@ -78,6 +77,8 @@ defmodule ExWebRTC.DTLSTransport do cert: cert, pkey: pkey, fingerprint: fingerprint, + # sha256 hex dump + peer_fingerprint: nil, srtp: srtp, dtls_state: :new, dtls: nil, @@ -100,22 +101,25 @@ defmodule ExWebRTC.DTLSTransport do end @impl true - def handle_call({:start_dtls, mode}, _from, %{dtls: nil} = state) + def handle_call({:start_dtls, mode, peer_fingerprint}, _from, %{dtls: nil} = state) when mode in [:active, :passive] do + ex_dtls_mode = if mode == :active, do: :client, else: :server + dtls = ExDTLS.init( - client_mode: mode == :active, + mode: ex_dtls_mode, dtls_srtp: true, pkey: state.pkey, - cert: state.cert + cert: state.cert, + verify_peer: true ) - state = %{state | dtls: dtls, mode: mode} + state = %{state | dtls: dtls, mode: mode, peer_fingerprint: peer_fingerprint} {:reply, :ok, state} end @impl true - def handle_call({:start_dtls, _mode}, _from, state) do + def handle_call({:start_dtls, _mode, _peer_fingerprint}, _from, state) do # is there a case when mode will change and new handshake will be needed? {:reply, {:error, :already_started}, state} end @@ -198,9 +202,20 @@ defmodule ExWebRTC.DTLSTransport do {:handshake_finished, _, remote_keying_material, profile, packets} -> Logger.debug("DTLS handshake finished") ICEAgent.send_data(state.ice_agent, packets) - # TODO: validate fingerprint - state = setup_srtp(state, remote_keying_material, profile) - update_dtls_state(state, :connected) + + peer_fingerprint = + state.dtls + |> ExDTLS.get_peer_cert() + |> ExDTLS.get_cert_fingerprint() + |> Utils.hex_dump() + + if peer_fingerprint == state.peer_fingerprint do + state = setup_srtp(state, remote_keying_material, profile) + update_dtls_state(state, :connected) + else + Logger.debug("Non-matching peer cert fingerprint.") + update_dtls_state(state, :failed) + end {:handshake_finished, _, remote_keying_material, profile} -> Logger.debug("DTLS handshake finished") @@ -283,6 +298,7 @@ defmodule ExWebRTC.DTLSTransport do defp update_dtls_state(%{dtls_state: dtls_state} = state, dtls_state), do: state defp update_dtls_state(state, new_dtls_state) do + Logger.debug("Changing DTLS state: #{state.dtls_state} -> #{new_dtls_state}") notify(state.owner, {:state_change, new_dtls_state}) %{state | dtls_state: new_dtls_state} end diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 73fbacd5..2aeb7854 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -61,7 +61,8 @@ defmodule ExWebRTC.PeerConnection do signaling_state: :stable, conn_state: :new, last_offer: nil, - last_answer: nil + last_answer: nil, + peer_fingerprint: nil ] #### API #### @@ -280,7 +281,7 @@ defmodule ExWebRTC.PeerConnection do {:ok, state} <- apply_local_description(other_type, sdp, state) do {:reply, :ok, %{state | signaling_state: next_state}} else - error -> {:reply, error, state} + {:error, _reason} = error -> {:reply, error, state} end end end @@ -299,7 +300,7 @@ defmodule ExWebRTC.PeerConnection do {:ok, state} <- apply_remote_description(other_type, sdp, state) do {:reply, :ok, %{state | signaling_state: next_state}} else - error -> {:reply, error, state} + {:error, _reason} = error -> {:reply, error, state} end end end @@ -351,7 +352,13 @@ defmodule ExWebRTC.PeerConnection do state = %__MODULE__{state | ice_state: new_ice_state} next_conn_state = next_conn_state(new_ice_state, state.dtls_state) state = update_conn_state(state, next_conn_state) - {:noreply, state} + + if next_conn_state == :failed do + Logger.debug("Stopping PeerConnection") + {:stop, {:shutdown, :conn_state_failed}, state} + else + {:noreply, state} + end end @impl true @@ -408,7 +415,7 @@ defmodule ExWebRTC.PeerConnection do dtls = if type == :answer do {:setup, setup} = ExSDP.Media.get_attribute(hd(sdp.media), :setup) - :ok = DTLSTransport.start_dtls(state.dtls_transport, setup) + :ok = DTLSTransport.start_dtls(state.dtls_transport, setup, state.peer_fingerprint) else state.dtls_transport end @@ -430,6 +437,7 @@ defmodule ExWebRTC.PeerConnection do with :ok <- SDPUtils.ensure_mid(sdp), :ok <- SDPUtils.ensure_bundle(sdp), {:ok, {ice_ufrag, ice_pwd}} <- SDPUtils.get_ice_credentials(sdp), + {:ok, {:fingerprint, {:sha256, peer_fingerprint}}} <- SDPUtils.get_cert_fingerprint(sdp), {:ok, new_transceivers} <- update_remote_transceivers(state.transceivers, sdp, state.config) do :ok = ICEAgent.set_remote_credentials(state.ice_agent, ice_ufrag, ice_pwd) @@ -460,12 +468,24 @@ defmodule ExWebRTC.PeerConnection do :passive -> :active end - :ok = DTLSTransport.start_dtls(state.dtls_transport, setup) + :ok = DTLSTransport.start_dtls(state.dtls_transport, setup, peer_fingerprint) else state.dtls_transport end - {:ok, %{state | transceivers: new_transceivers, dtls_transport: dtls}} + {:ok, + %{ + state + | transceivers: new_transceivers, + dtls_transport: dtls, + peer_fingerprint: peer_fingerprint + }} + else + {:ok, {:fingerprint, {_hash_function, _fingerprint}}} -> + {:error, :unsupported_cert_fingerprint_hash_function} + + {:error, _reason} = error -> + error end end @@ -569,6 +589,7 @@ defmodule ExWebRTC.PeerConnection do defp update_conn_state(%{conn_state: conn_state} = state, conn_state), do: state defp update_conn_state(state, new_conn_state) do + Logger.debug("Changing PeerConnection state: #{state.conn_state} -> #{new_conn_state}") notify(state.owner, {:connection_state_change, new_conn_state}) %{state | conn_state: new_conn_state} end diff --git a/lib/ex_webrtc/sdp_utils.ex b/lib/ex_webrtc/sdp_utils.ex index ad3f2c8d..bee9bb99 100644 --- a/lib/ex_webrtc/sdp_utils.ex +++ b/lib/ex_webrtc/sdp_utils.ex @@ -11,6 +11,26 @@ defmodule ExWebRTC.SDPUtils do end) end + @spec delete_attribute(ExSDP.t() | ExSDP.Media.t(), module() | atom() | binary()) :: + ExSDP.t() | ExSDP.Media.t() + def delete_attribute(sdp_or_mline, key) do + delete_attributes(sdp_or_mline, [key]) + end + + @spec delete_attributes(ExSDP.t() | ExSDP.Media.t(), [module() | atom() | binary()]) :: + ExSDP.t() | ExSDP.Media.t() + def delete_attributes(sdp_or_mline, attributes) when is_list(attributes) do + new_attrs = + Enum.reject(sdp_or_mline.attributes, fn + %module{} -> module in attributes + {k, _v} -> k in attributes + # flag attributes + k -> k in attributes + end) + + Map.put(sdp_or_mline, :attributes, new_attrs) + end + @spec ensure_mid(ExSDP.t()) :: :ok | {:error, :missing_mid | :duplicated_mid} def ensure_mid(sdp) do sdp.media @@ -101,6 +121,34 @@ defmodule ExWebRTC.SDPUtils do end end + @spec get_cert_fingerprint(ExSDP.t()) :: + {:ok, {:fingerprint, {:sha256, binary()}}} + | {:error, :missing_cert_fingerprint | :conflicting_cert_fingerprints} + def get_cert_fingerprint(sdp) do + session_fingerprint = do_get_cert_fingerprint(sdp) + mline_fingerprints = Enum.map(sdp.media, fn mline -> do_get_cert_fingerprint(mline) end) + + case {session_fingerprint, mline_fingerprints} do + {nil, []} -> + {:error, :missing_cert_fingerprint} + + {session_fingerprint, []} -> + {:ok, session_fingerprint} + + {nil, mline_fingerprints} -> + with :ok <- ensure_fingerprints_present(mline_fingerprints), + :ok <- ensure_fingerprints_unique(mline_fingerprints) do + {:ok, List.first(mline_fingerprints)} + end + + {session_fingerprint, mline_fingerprints} -> + with :ok <- ensure_fingerprints_present(mline_fingerprints), + :ok <- ensure_fingerprints_unique([session_fingerprint | mline_fingerprints]) do + {:ok, session_fingerprint} + end + end + end + @spec get_extensions(ExSDP.t()) :: %{(id :: non_neg_integer()) => extension :: module() | {SourceDescription, atom()}} def get_extensions(sdp) do @@ -156,6 +204,16 @@ defmodule ExWebRTC.SDPUtils do {ice_ufrag, ice_pwd} end + defp do_get_cert_fingerprint(sdp_or_mline) do + get_attr = + case sdp_or_mline do + %ExSDP{} -> &ExSDP.get_attribute/2 + %ExSDP.Media{} -> &ExSDP.Media.get_attribute/2 + end + + get_attr.(sdp_or_mline, :fingerprint) + end + defp ensure_ice_credentials_present(creds) do creds |> Enum.find(fn {ice_ufrag, ice_pwd} -> ice_ufrag == nil or ice_pwd == nil end) @@ -180,4 +238,19 @@ defmodule ExWebRTC.SDPUtils do _ -> {:error, :conflicting_ice_credentials} end end + + defp ensure_fingerprints_present(fingerprints) do + if Enum.all?(fingerprints, &(&1 != nil)) do + :ok + else + {:error, :missing_cert_fingerprint} + end + end + + defp ensure_fingerprints_unique(fingerprints) do + case Enum.uniq(fingerprints) do + [_] -> :ok + _ -> {:error, :conflicting_cert_fingerprints} + end + end end diff --git a/mix.exs b/mix.exs index 06d7795c..b4aead36 100644 --- a/mix.exs +++ b/mix.exs @@ -47,7 +47,7 @@ defmodule ExWebRTC.MixProject do [ {:ex_sdp, "~> 0.13.0"}, {:ex_ice, "~> 0.2.0"}, - {:ex_dtls, "~> 0.14.0"}, + {:ex_dtls, github: "elixir-webrtc/ex_dtls"}, {:ex_libsrtp, "~> 0.6.0"}, {:ex_rtp, "~> 0.2.0"}, {:ex_rtcp, "~> 0.1.0"}, diff --git a/mix.lock b/mix.lock index 7bfc9044..aef0377a 100644 --- a/mix.lock +++ b/mix.lock @@ -5,12 +5,12 @@ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.34", "b0fbb4fd333ee7e9babc07e9573796850759cd12796fcf2fec59cf0031cbaad9", [:mix], [], "hexpm", "cc0d7a6f2367e4504867b4ec38ceee24e89ee6bca9c7b94a6d940f54aba2e8d5"}, + "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, + "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.38", "b42252eddf63bda05554ba8be93a1262dc0920c721f1aaf989f5de0f73a2e367", [:mix], [], "hexpm", "2cd0907795aaef0c7e8442e376633c5b3bd6edc8dbbdc539b22f095501c1cdb6"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, - "ex_dtls": {:hex, :ex_dtls, "0.14.0", "f2e589a24396599551c6b142c3a6a7a55f7fa772c90716888ed42bf3c994cb2d", [:mix], [{:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "7fa79815d8dbcee3b1464c3590527fb85fae9592b6e092f2554c5974e2c5847a"}, + "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, + "ex_dtls": {:git, "https://github.com/elixir-webrtc/ex_dtls.git", "876e2119f97eee053671b6f6fd069575f0da8d77", []}, "ex_ice": {:hex, :ex_ice, "0.2.0", "6002de5b1c0561f8ff50238a6d04cb99eabf9b7db91031b6f0580d67888f0365", [:mix], [{:ex_stun, "~> 0.1.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "1def9449009241918b31ff269f344e5c4bd15257ba5cf95d1edc1250c28af39b"}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.6.0", "d96cd7fc1780157614f0bf47d31587e5eab953b43067f4885849f8177ec452a9", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e9ce8a507a658f7e2df72fae82a4b3ba0a056c175f0bc490e79ab03058e094d5"}, "ex_rctp": {:git, "https://github.com/elixir-webrtc/ex_rtcp.git", "c0cf2b7f995e34d13cee4cbb228376a55700fb6a", []}, @@ -25,7 +25,7 @@ "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, @@ -33,7 +33,7 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, diff --git a/test/ex_webrtc/dtls_transport_test.exs b/test/ex_webrtc/dtls_transport_test.exs index a5e85aa7..e135d68b 100644 --- a/test/ex_webrtc/dtls_transport_test.exs +++ b/test/ex_webrtc/dtls_transport_test.exs @@ -1,7 +1,11 @@ defmodule ExWebRTC.DTLSTransportTest do use ExUnit.Case, async: true - alias ExWebRTC.DTLSTransport + alias ExWebRTC.{DTLSTransport, Utils} + + @fingerprint cert + |> ExDTLS.get_cert_fingerprint() + |> Utils.hex_dump() defmodule FakeICEAgent do use GenServer @@ -61,12 +65,12 @@ defmodule ExWebRTC.DTLSTransportTest do end test "cannot start dtls more than once", %{dtls: dtls} do - assert :ok = DTLSTransport.start_dtls(dtls, :passive) - assert {:error, :already_started} = DTLSTransport.start_dtls(dtls, :passive) + assert :ok = DTLSTransport.start_dtls(dtls, :passive, @fingerprint) + assert {:error, :already_started} = DTLSTransport.start_dtls(dtls, :passive, @fingerprint) end test "initiates DTLS handshake when in active mode", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :active) + :ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint) FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) @@ -75,7 +79,7 @@ defmodule ExWebRTC.DTLSTransportTest do end test "won't initiate DTLS handshake when in passive mode", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :passive) + :ok = DTLSTransport.start_dtls(dtls, :passive, @fingerprint) FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) @@ -83,7 +87,7 @@ defmodule ExWebRTC.DTLSTransportTest do end test "will retransmit after initiating handshake", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :active) + :ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint) FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) @@ -94,9 +98,9 @@ defmodule ExWebRTC.DTLSTransportTest do end test "will buffer packets and send when connected", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :passive) + :ok = DTLSTransport.start_dtls(dtls, :passive, @fingerprint) - remote_dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) + remote_dtls = ExDTLS.init(mode: :client, dtls_srtp: true) {packets, _timeout} = ExDTLS.do_handshake(remote_dtls) FakeICEAgent.send_dtls(ice, {:data, packets}) @@ -108,8 +112,8 @@ defmodule ExWebRTC.DTLSTransportTest do end test "finishes handshake in active mode", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :active) - remote_dtls = ExDTLS.init(client_mode: false, dtls_srtp: true) + :ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint) + remote_dtls = ExDTLS.init(mode: :server, dtls_srtp: true) FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) @@ -119,11 +123,19 @@ defmodule ExWebRTC.DTLSTransportTest do end test "finishes handshake in passive mode", %{dtls: dtls, ice: ice} do - :ok = DTLSTransport.start_dtls(dtls, :passive) - FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) + remote_dtls = ExDTLS.init(mode: :client, dtls_srtp: true) + + remote_fingerprint = + remote_dtls + |> ExDTLS.get_cert() + |> ExDTLS.get_cert_fingerprint() + |> Utils.hex_dump() + + :ok = DTLSTransport.start_dtls(dtls, :passive, remote_fingerprint) - remote_dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) {packets, _timeout} = ExDTLS.do_handshake(remote_dtls) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) + FakeICEAgent.send_dtls(ice, {:data, packets}) assert :ok == check_handshake(dtls, ice, remote_dtls) diff --git a/test/ex_webrtc/peer_connection_test.exs b/test/ex_webrtc/peer_connection_test.exs index 2f0aa21c..71629581 100644 --- a/test/ex_webrtc/peer_connection_test.exs +++ b/test/ex_webrtc/peer_connection_test.exs @@ -1,13 +1,29 @@ defmodule ExWebRTC.PeerConnectionTest do use ExUnit.Case, async: true - alias ExWebRTC.{MediaStreamTrack, PeerConnection, SessionDescription} + alias ExWebRTC.{MediaStreamTrack, PeerConnection, SessionDescription, SDPUtils, Utils} + + {_pkey, cert} = ExDTLS.generate_key_cert() + + @fingerprint cert + |> ExDTLS.get_cert_fingerprint() + |> Utils.hex_dump() @audio_mline ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [108]) - |> ExSDP.Media.add_attributes(mid: "0", ice_ufrag: "someufrag", ice_pwd: "somepwd") + |> ExSDP.Media.add_attributes( + mid: "0", + ice_ufrag: "someufrag", + ice_pwd: "somepwd", + fingerprint: {:sha256, @fingerprint} + ) @video_mline ExSDP.Media.new("video", 9, "UDP/TLS/RTP/SAVPF", [96]) - |> ExSDP.Media.add_attributes(mid: "1", ice_ufrag: "someufrag", ice_pwd: "somepwd") + |> ExSDP.Media.add_attributes( + mid: "1", + ice_ufrag: "someufrag", + ice_pwd: "somepwd", + fingerprint: {:sha256, @fingerprint} + ) test "track notification" do {:ok, pc} = PeerConnection.start_link() @@ -143,7 +159,7 @@ defmodule ExWebRTC.PeerConnectionTest do offer = %SessionDescription{type: :offer, sdp: sdp} assert {:error, :duplicated_mid} = PeerConnection.set_remote_description(pc, offer) - mline = ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [96]) + mline = SDPUtils.delete_attribute(@audio_mline, :mid) sdp = ExSDP.add_media(raw_sdp, mline) |> to_string() offer = %SessionDescription{type: :offer, sdp: sdp} assert {:error, :missing_mid} = PeerConnection.set_remote_description(pc, offer) @@ -172,6 +188,9 @@ defmodule ExWebRTC.PeerConnectionTest do raw_sdp = ExSDP.new() + audio_mline = SDPUtils.delete_attributes(@audio_mline, [:ice_ufrag, :ice_pwd]) + video_mline = SDPUtils.delete_attributes(@video_mline, [:ice_ufrag, :ice_pwd]) + [ {{nil, nil}, {"someufrag", "somepwd"}, {"someufrag", "somepwd"}, :ok}, {{"someufrag", "somepwd"}, {"someufrag", "somepwd"}, {nil, nil}, :ok}, @@ -185,14 +204,6 @@ defmodule ExWebRTC.PeerConnectionTest do {:error, :conflicting_ice_credentials}} ] |> Enum.each(fn {{s_ufrag, s_pwd}, {a_ufrag, a_pwd}, {v_ufrag, v_pwd}, expected_result} -> - audio_mline = - ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [108]) - |> ExSDP.Media.add_attributes(mid: "0") - - video_mline = - ExSDP.Media.new("video", 9, "UDP/TLS/RTP/SAVPF", [96]) - |> ExSDP.Media.add_attributes(mid: "1") - audio_mline = if a_ufrag do ExSDP.Media.add_attribute(audio_mline, {:ice_ufrag, a_ufrag}) @@ -248,5 +259,59 @@ defmodule ExWebRTC.PeerConnectionTest do assert expected_result == PeerConnection.set_remote_description(pc, offer) end) end + + test "cert fingerprint" do + {:ok, pc} = PeerConnection.start_link() + raw_sdp = ExSDP.new() + + audio_mline = SDPUtils.delete_attribute(@audio_mline, :fingerprint) + video_mline = SDPUtils.delete_attribute(@video_mline, :fingerprint) + + [ + {{:sha256, @fingerprint}, {:sha256, @fingerprint}, {:sha256, @fingerprint}, :ok}, + {nil, {:sha256, @fingerprint}, {:sha256, @fingerprint}, :ok}, + {{:sha256, @fingerprint}, nil, {:sha256, @fingerprint}, + {:error, :missing_cert_fingerprint}}, + {nil, {:sha256, @fingerprint}, nil, {:error, :missing_cert_fingerprint}}, + {nil, {:sha256, @fingerprint}, {:sha1, @fingerprint}, + {:error, :conflicting_cert_fingerprints}}, + {nil, {:sha1, @fingerprint}, {:sha1, @fingerprint}, + {:error, :unsupported_cert_fingerprint_hash_function}} + ] + |> Enum.each(fn {s_fingerprint, a_fingerprint, v_fingerprint, expected_result} -> + audio_mline = + if a_fingerprint do + ExSDP.Media.add_attribute(audio_mline, {:fingerprint, a_fingerprint}) + else + audio_mline + end + + video_mline = + if v_fingerprint do + ExSDP.Media.add_attribute(video_mline, {:fingerprint, v_fingerprint}) + else + video_mline + end + + sdp = + ExSDP.add_attribute(raw_sdp, %ExSDP.Attribute.Group{semantics: "BUNDLE", mids: [0, 1]}) + + sdp = + if s_fingerprint do + ExSDP.add_attribute(sdp, {:fingerprint, s_fingerprint}) + else + sdp + end + + sdp = + sdp + |> ExSDP.add_media([audio_mline, video_mline]) + |> to_string() + + offer = %SessionDescription{type: :offer, sdp: sdp} + + assert expected_result == PeerConnection.set_remote_description(pc, offer) + end) + end end end