Skip to content

Commit

Permalink
Allow for sending data over DTLS (#45)
Browse files Browse the repository at this point in the history
Add `ExDTLS.write_data` function
  • Loading branch information
LVala authored Aug 20, 2024
1 parent f189242 commit dff8ec1
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 9 deletions.
35 changes: 35 additions & 0 deletions c_src/ex_dtls/native.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,41 @@ UNIFEX_TERM do_handshake(UnifexEnv *env, State *state) {
return unifex_raise(state->env, "Handshake failed: no packets generated");
}

UNIFEX_TERM write_data(UnifexEnv *env, State *state, UnifexPayload *payload) {
if (state->hsk_finished != 1) {
DEBUG("Cannot write, handshake not finished");
return write_data_result_error_handshake_not_finished(env);
}

int ret = SSL_write(state->ssl, payload->data, payload->size);
if (ret <= 0) {
DEBUG("Unable to write data");
return unifex_raise(env, "Unable to write data");
}

BIO *wbio = SSL_get_wbio(state->ssl);
size_t pending_data_len = BIO_ctrl_pending(wbio);
if (pending_data_len == 0) {
DEBUG("No data to read from BIO after writing");
return unifex_raise(env, "No data to read from BIO after writing");
}

UnifexPayload res_payload;
unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, pending_data_len, &res_payload);

int read_bytes = BIO_read(wbio, res_payload.data, pending_data_len);
if (read_bytes <= 0 || (size_t) read_bytes != pending_data_len) {
DEBUG("Unable to read data from BIO after writing");
return unifex_raise(env, "Unable to read data from BIO after writing");
}

DEBUG("Wrote %d bytes of data", read_bytes);
res_payload.size = (unsigned int) pending_data_len;
UNIFEX_TERM res_term = write_data_result_ok(env, &res_payload);
unifex_payload_release(&res_payload);
return res_term;
}

UNIFEX_TERM handle_data(UnifexEnv *env, State *state, UnifexPayload *payload) {
(void)env;

Expand Down
2 changes: 2 additions & 0 deletions c_src/ex_dtls/native.spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ spec do_handshake(state) :: {packets :: payload, timeout :: int}

spec handle_timeout(state) :: (:ok :: label) | {:retransmit :: label, packets :: payload, timeout :: int}

spec write_data(state, packets :: payload) :: {:ok :: label, packets :: payload} | {:error :: label, :handshake_not_finished :: label}

spec handle_data(state, packets :: payload) ::
{:ok :: label, packets :: payload}
| (:handshake_want_read :: label)
Expand Down
17 changes: 13 additions & 4 deletions lib/ex_dtls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule ExDTLS do
Module that allows performing DTLS handshake including a DTLS-SRTP one.
`ExDTLS` executes native OpenSSL functions to perform DTLS handshake.
It doesn't create or require any socket.
It doesn't create or require any socket.
Instead, it returns generated DTLS packets, which then have to be transported to the peer.
"""

Expand Down Expand Up @@ -82,7 +82,7 @@ defmodule ExDTLS do
This is always 2048-bit RSA key.
`not_before` and `not_after` can be used to
`not_before` and `not_after` can be used to
specify certificate duration in seconds.
They have to fit into architecture-dependent integer size.
Defaults to (-1 year) - (+ 1 year).
Expand Down Expand Up @@ -111,7 +111,7 @@ defmodule ExDTLS do
@doc """
Gets peer certificate.
Returns DER representation in binary format or `nil`
Returns DER representation in binary format or `nil`
when no certificate was presented by the peer or no connection
was established.
"""
Expand Down Expand Up @@ -142,14 +142,23 @@ defmodule ExDTLS do
@spec do_handshake(dtls()) :: {packets :: binary(), timeout :: integer()}
defdelegate do_handshake(dtls), to: Native

@doc """
Writes data to the DTLS connection.
Generates encrypted packets that need to be passed to the second host.
"""
@spec write_data(dtls(), packets :: binary()) ::
{:ok, packets :: binary()} | {:error, :handshake_not_finished}
defdelegate write_data(dtls, packets), to: Native

@doc """
Handles peer's packets.
When handshake is finished, calling `handle_data` will return `{:ok, binary()}`,
which is decoded data.
`:handshake_packets` contains handshake data that has to be sent to the peer.
`:handshake_want_read` means some additional data is needed for continuing handshake.
`:handshake_want_read` means some additional data is needed for continuing handshake.
It can be returned when retransmitted packet was passed but timer didn't expired yet.
`timeout` is a time in ms after which `handle_timeout/1` should be called.
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ defmodule ExDTLS.Mixfile do
defp deps do
[
{:unifex, "~> 1.0"},
{:bundlex, "~> 1.5.3", override: true},
{:ex_doc, "~> 0.29", only: :dev, runtime: false},
{:dialyxir, "~> 1.0", only: :dev, runtime: false},
{:credo, "~> 1.7", only: :dev, runtime: false},
Expand Down
8 changes: 4 additions & 4 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%{
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},
"bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"},
"bundlex": {:hex, :bundlex, "1.5.1", "a85890a9d0a70366afa538c8589a4ba75e1319d32a771e1f5f3b7566beea9c26", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, "~> 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "aae447d63230fe1f3b788c429ac02bc696f30163d0f23f52fcfe6ed38372c7ea"},
"bundlex": {:hex, :bundlex, "1.5.3", "35d01e5bc0679510dd9a327936ffb518f63f47175c26a35e708cc29eaec0890b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "debd0eac151b404f6216fc60222761dff049bf26f7d24d066c365317650cd118"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
Expand All @@ -14,18 +14,18 @@
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [: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", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
"req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"},
"req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"},
"shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
Expand Down
23 changes: 22 additions & 1 deletion test/integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,31 @@ defmodule ExDTLS.IntegrationTest do

assert ExDTLS.get_peer_cert(tx_dtls) == ExDTLS.get_cert(rx_dtls)
# Client only sends its certificate when requested to do so by the server.
# Because `verify_peer` is set to `false`, server won't ask for the client's certificate.
# Because `verify_peer` is set to `false`, server won't ask for the client's certificate.
assert ExDTLS.get_peer_cert(rx_dtls) == nil
end

test "sending over DTLS" do
sr_dtls = ExDTLS.init(mode: :server, dtls_srtp: true, verify_peer: true)
cl_dtls = ExDTLS.init(mode: :client, dtls_srtp: true, verify_peer: true)

assert {:error, :handshake_not_finished} = ExDTLS.write_data(sr_dtls, <<1, 2, 3>>)
assert {:error, :handshake_not_finished} = ExDTLS.write_data(cl_dtls, <<1, 2, 3>>)

{packets, _timeout} = ExDTLS.do_handshake(cl_dtls)
assert :ok == loop({sr_dtls, false}, {cl_dtls, false}, packets)

msg = <<1, 3, 2, 5>>
assert {:ok, data} = ExDTLS.write_data(cl_dtls, msg)
assert data != msg
assert {:ok, ^msg} = ExDTLS.handle_data(sr_dtls, data)

msg = <<1, 3, 8, 9>>
assert {:ok, data} = ExDTLS.write_data(sr_dtls, msg)
assert data != msg
assert {:ok, ^msg} = ExDTLS.handle_data(cl_dtls, data)
end

test "expired cert" do
# generate expired cert
{key, cert} = ExDTLS.generate_key_cert(-1, 0)
Expand Down

0 comments on commit dff8ec1

Please sign in to comment.