├── .clang-format ├── .formatter.exs ├── .github └── workflows │ └── elixir.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── c_src └── olm_nif.c ├── lib ├── olm.ex └── olm │ ├── account.ex │ ├── nif.ex │ ├── session.ex │ └── utility.ex ├── mix.exs ├── mix.lock └── test ├── olm ├── account_test.exs ├── session_test.exs └── utility_test.exs ├── olm_test.exs └── test_helper.exs /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | 4 | UseTab: Never 5 | TabWidth: 4 6 | ContinuationIndentWidth: 4 7 | IndentWidth: 4 8 | ColumnLimit: 80 9 | 10 | SortIncludes: true 11 | 12 | PointerAlignment: Right 13 | 14 | SpaceAfterCStyleCast: true 15 | 16 | AlignAfterOpenBracket: Align 17 | AlignConsecutiveAssignments: true 18 | AlignConsecutiveDeclarations: true 19 | AlignConsecutiveMacros: true 20 | AlignEscapedNewlines: Left 21 | AlignOperands: true 22 | AlignTrailingComments: true 23 | 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | BreakBeforeBinaryOperators: None 27 | BreakBeforeBraces: Mozilla 28 | BreakBeforeTernaryOperators: false 29 | BreakStringLiterals: false 30 | AlwaysBreakAfterReturnType: TopLevel 31 | AlwaysBreakBeforeMultilineStrings: false 32 | BraceWrapping: 33 | AfterEnum: true 34 | 35 | AllowAllParametersOfDeclarationOnNextLine: true 36 | AllowShortBlocksOnASingleLine: Always 37 | AllowShortCaseLabelsOnASingleLine: false 38 | AllowShortFunctionsOnASingleLine: InlineOnly 39 | AllowShortIfStatementsOnASingleLine: WithoutElse 40 | AllowShortLoopsOnASingleLine: true 41 | 42 | IndentCaseLabels: false 43 | IndentPPDirectives: None 44 | KeepEmptyLinesAtTheStartOfBlocks: false 45 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | name: Elixir CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Setup elixir 18 | uses: erlef/setup-beam@v1 19 | with: 20 | elixir-version: 1.12.1 21 | otp-version: 24.0 22 | 23 | - name: Install Olm C library 24 | run: sudo apt-get update && sudo apt-get install libolm-dev 25 | 26 | - name: Install Dependencies 27 | run: mix deps.get 28 | 29 | - name: Run Tests 30 | run: mix compile && mix test 31 | 32 | - name: Check formatting 33 | run: mix format --check-formatted 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | olm-*.tar 24 | 25 | # Ignore shared object files 26 | *.so 27 | 28 | # Ignore VS code project configuration 29 | .vscode 30 | 31 | .iex.exs 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Niklas Long 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCH := $(shell uname -s) 2 | PREFIX ?= ./priv 3 | 4 | ERL_INCLUDE_PATH=$(shell erl -eval 'io:format("~s~n", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell) 5 | 6 | CFLAGS ?= -fPIC -shared -I$(ERL_INCLUDE_PATH) 7 | LDFLAGS ?= -lolm 8 | 9 | ifeq ($(ARCH), Darwin) 10 | LDFLAGS += -dynamiclib -undefined dynamic_lookup 11 | endif 12 | 13 | all: $(PREFIX)/olm_nif.so 14 | 15 | $(PREFIX)/olm_nif.so: c_src/olm_nif.c 16 | @mkdir -p "$(@D)" 17 | cc $(CFLAGS) -o $@ $< $(LDFLAGS) 18 | 19 | clean: 20 | rm -rf $(PREFIX) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Olm 2 | 3 | Elixir wrapper for [Olm](https://gitlab.matrix.org/matrix-org/olm), an 4 | implementation of the Double Ratchet cryptographic ratchet. 5 | 6 | ## Installation 7 | 8 | Olm is a native C library. The library is packaged by several distributions. 9 | 10 | On Debian one can install it like so: 11 | 12 | apt install libolm-dev 13 | 14 | On Darwin: 15 | 16 | brew install libolm 17 | 18 | If you're on Apple Silicon you may also need to (see https://github.com/niklaslong/olm-elixir/issues/35): 19 | 20 | ``` 21 | export CPATH=/opt/homebrew/include 22 | export LIBRARY_PATH=/opt/homebrew/lib 23 | ``` 24 | 25 | The package can be installed by adding `olm` to your list of dependencies in 26 | `mix.exs`: 27 | 28 | ```elixir 29 | def deps do 30 | [ 31 | {:olm, "~> 0.1.0-rc"} 32 | ] 33 | end 34 | ``` 35 | 36 | The docs can be found at [https://hexdocs.pm/olm](https://hexdocs.pm/olm). 37 | -------------------------------------------------------------------------------- /c_src/olm_nif.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Resource setup 6 | 7 | static ErlNifResourceType *account_resource; 8 | static ErlNifResourceType *session_resource; 9 | 10 | void 11 | account_dtor(ErlNifEnv *caller_env, void *account) 12 | { 13 | olm_clear_account(account); 14 | } 15 | 16 | void 17 | session_dtor(ErlNifEnv *caller_env, void *session) 18 | { 19 | olm_clear_session(session); 20 | } 21 | 22 | static int 23 | nif_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) 24 | { 25 | int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; 26 | 27 | account_resource = enif_open_resource_type( 28 | env, NULL, "account", account_dtor, flags, NULL); 29 | 30 | session_resource = enif_open_resource_type( 31 | env, NULL, "session", session_dtor, flags, NULL); 32 | 33 | return 0; 34 | } 35 | 36 | static ERL_NIF_TERM 37 | version(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 38 | { 39 | uint8_t major, minor, patch; 40 | olm_get_library_version(&major, &minor, &patch); 41 | 42 | return enif_make_tuple3(env, 43 | enif_make_uint(env, major), 44 | enif_make_uint(env, minor), 45 | enif_make_uint(env, patch)); 46 | } 47 | 48 | // Accounts 49 | 50 | static ERL_NIF_TERM 51 | create_account(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 52 | { 53 | size_t account_size = olm_account_size(); 54 | 55 | // Allocate memory based on account_size. 56 | OlmAccount *memory = enif_alloc_resource(account_resource, account_size); 57 | OlmAccount *account = olm_account(memory); 58 | 59 | size_t random_length = olm_create_account_random_length(account); 60 | char bytes[random_length]; 61 | 62 | size_t result = olm_create_account(account, bytes, random_length); 63 | 64 | // Return {:ok, account_ref} or {:error, last_error}. 65 | if (result == olm_error()) { 66 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 67 | ERL_NIF_TERM error_message = enif_make_string( 68 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 69 | 70 | enif_release_resource(account); 71 | 72 | return enif_make_tuple2(env, error_atom, error_message); 73 | } 74 | 75 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 76 | ERL_NIF_TERM term = enif_make_resource(env, account); 77 | enif_release_resource(account); 78 | 79 | return enif_make_tuple2(env, ok_atom, term); 80 | } 81 | 82 | static ERL_NIF_TERM 83 | pickle_account(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 84 | { 85 | ErlNifBinary key; 86 | ErlNifBinary pickled; 87 | 88 | OlmAccount *account; 89 | 90 | // Read args. 91 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 92 | enif_inspect_binary(env, argv[1], &key); 93 | 94 | // Allocate buffer for result. 95 | size_t pickled_length = olm_pickle_account_length(account); 96 | enif_alloc_binary(pickled_length, &pickled); 97 | 98 | size_t result = olm_pickle_account( 99 | account, key.data, key.size, pickled.data, pickled_length); 100 | 101 | // Return {:ok, pickled} or {:error, last_error}. 102 | if (result == olm_error()) { 103 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 104 | ERL_NIF_TERM error_message = enif_make_string( 105 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 106 | 107 | enif_release_binary(&pickled); 108 | 109 | return enif_make_tuple2(env, error_atom, error_message); 110 | } 111 | 112 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 113 | ERL_NIF_TERM term = enif_make_binary(env, &pickled); 114 | 115 | return enif_make_tuple2(env, ok_atom, term); 116 | } 117 | 118 | static ERL_NIF_TERM 119 | unpickle_account(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 120 | { 121 | ErlNifBinary pickled, pickled_input; 122 | ErlNifBinary key; 123 | 124 | // Read args. 125 | enif_inspect_binary(env, argv[0], &pickled_input); 126 | enif_alloc_binary(pickled_input.size, &pickled); 127 | memcpy(pickled.data, pickled_input.data, pickled_input.size); 128 | 129 | enif_inspect_binary(env, argv[1], &key); 130 | 131 | // Initialise account memory. 132 | size_t account_size = olm_account_size(); 133 | OlmAccount *memory = enif_alloc_resource(account_resource, account_size); 134 | OlmAccount *account = olm_account(memory); 135 | 136 | size_t result = olm_unpickle_account( 137 | account, key.data, key.size, pickled.data, pickled.size); 138 | 139 | // Return {:ok, account_ref} or {:error, last_error}. 140 | if (result == olm_error()) { 141 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 142 | ERL_NIF_TERM error_message = enif_make_string( 143 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 144 | 145 | enif_release_resource(account); 146 | enif_release_binary(&pickled); 147 | 148 | return enif_make_tuple2(env, error_atom, error_message); 149 | } 150 | 151 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 152 | ERL_NIF_TERM term = enif_make_resource(env, account); 153 | 154 | enif_release_resource(account); 155 | enif_release_binary(&pickled); 156 | 157 | return enif_make_tuple2(env, ok_atom, term); 158 | } 159 | 160 | static ERL_NIF_TERM 161 | account_identity_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 162 | { 163 | OlmAccount *account; 164 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 165 | 166 | // Allocate memory for identity keys. 167 | ErlNifBinary identity_keys; 168 | size_t keys_length = olm_account_identity_keys_length(account); 169 | enif_alloc_binary(keys_length, &identity_keys); 170 | 171 | size_t result = olm_account_identity_keys( 172 | account, identity_keys.data, identity_keys.size); 173 | 174 | // Returns {:ok, identity_keys} or {:error, last_error}. 175 | if (result == olm_error()) { 176 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 177 | ERL_NIF_TERM error_message = enif_make_string( 178 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 179 | 180 | enif_release_binary(&identity_keys); 181 | 182 | return enif_make_tuple2(env, error_atom, error_message); 183 | } 184 | 185 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 186 | ERL_NIF_TERM term = enif_make_binary(env, &identity_keys); 187 | 188 | return enif_make_tuple2(env, ok_atom, term); 189 | } 190 | 191 | static ERL_NIF_TERM 192 | account_sign(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 193 | { 194 | OlmAccount *account; 195 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 196 | 197 | ErlNifBinary message; 198 | enif_inspect_binary(env, argv[1], &message); 199 | 200 | ErlNifBinary signature; 201 | size_t signature_length = olm_account_signature_length(account); 202 | enif_alloc_binary(signature_length, &signature); 203 | 204 | size_t result = olm_account_sign( 205 | account, message.data, message.size, signature.data, signature.size); 206 | 207 | // Returns {:ok, signed} or {:error, last_error}. 208 | if (result == olm_error()) { 209 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 210 | ERL_NIF_TERM error_message = enif_make_string( 211 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 212 | 213 | enif_release_binary(&signature); 214 | 215 | return enif_make_tuple2(env, error_atom, error_message); 216 | } 217 | 218 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 219 | ERL_NIF_TERM term = enif_make_binary(env, &signature); 220 | 221 | return enif_make_tuple2(env, ok_atom, term); 222 | } 223 | 224 | static ERL_NIF_TERM 225 | account_one_time_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 226 | { 227 | OlmAccount *account; 228 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 229 | 230 | ErlNifBinary one_time_keys; 231 | size_t one_time_keys_length = olm_account_one_time_keys_length(account); 232 | enif_alloc_binary(one_time_keys_length, &one_time_keys); 233 | 234 | size_t result = olm_account_one_time_keys( 235 | account, one_time_keys.data, one_time_keys.size); 236 | 237 | // Returns {:ok, one_time_keys} or {:error, last_error}. 238 | if (result == olm_error()) { 239 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 240 | ERL_NIF_TERM error_message = enif_make_string( 241 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 242 | 243 | enif_release_binary(&one_time_keys); 244 | 245 | return enif_make_tuple2(env, error_atom, error_message); 246 | } 247 | 248 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 249 | ERL_NIF_TERM term = enif_make_binary(env, &one_time_keys); 250 | 251 | return enif_make_tuple2(env, ok_atom, term); 252 | } 253 | 254 | static ERL_NIF_TERM 255 | account_mark_keys_as_published(ErlNifEnv *env, 256 | int argc, 257 | const ERL_NIF_TERM argv[]) 258 | { 259 | OlmAccount *account; 260 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 261 | 262 | size_t result = olm_account_mark_keys_as_published(account); 263 | 264 | // Returns {:ok, 'SUCCESS'} or {:error, last_error}. 265 | if (result == olm_error()) { 266 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 267 | ERL_NIF_TERM error_message = enif_make_string( 268 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 269 | 270 | return enif_make_tuple2(env, error_atom, error_message); 271 | } 272 | 273 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 274 | ERL_NIF_TERM msg = enif_make_string( 275 | env, "Successfully marked keys as published", ERL_NIF_LATIN1); 276 | 277 | return enif_make_tuple2(env, ok_atom, msg); 278 | } 279 | 280 | static ERL_NIF_TERM 281 | account_max_one_time_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 282 | { 283 | OlmAccount *account; 284 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 285 | 286 | size_t max = olm_account_max_number_of_one_time_keys(account); 287 | 288 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 289 | ERL_NIF_TERM term = enif_make_ulong(env, max); 290 | 291 | return enif_make_tuple2(env, ok_atom, term); 292 | } 293 | 294 | static ERL_NIF_TERM 295 | account_generate_one_time_keys(ErlNifEnv *env, 296 | int argc, 297 | const ERL_NIF_TERM argv[]) 298 | { 299 | // Get args. 300 | OlmAccount *account; 301 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 302 | 303 | size_t count; 304 | enif_get_ulong(env, argv[1], &count); 305 | 306 | size_t random_length = 307 | olm_account_generate_one_time_keys_random_length(account, count); 308 | 309 | // Needs more randomness? 310 | char random[random_length]; 311 | size_t result = olm_account_generate_one_time_keys( 312 | account, count, random, random_length); 313 | 314 | ERL_NIF_TERM result_atom; 315 | 316 | if (result == olm_error()) { 317 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 318 | ERL_NIF_TERM error_message = enif_make_string( 319 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 320 | 321 | return enif_make_tuple2(env, error_atom, error_message); 322 | } 323 | 324 | result_atom = enif_make_atom(env, "ok"); 325 | ERL_NIF_TERM msg = 326 | enif_make_string(env, "Successfully generated", ERL_NIF_LATIN1); 327 | 328 | return enif_make_tuple2(env, result_atom, msg); 329 | } 330 | 331 | static ERL_NIF_TERM 332 | remove_one_time_keys(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 333 | { 334 | OlmAccount *account; 335 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 336 | 337 | OlmSession *session; 338 | enif_get_resource(env, argv[1], session_resource, (void **) &session); 339 | 340 | size_t result = olm_remove_one_time_keys(account, session); 341 | 342 | if (result == olm_error()) { 343 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 344 | ERL_NIF_TERM error_message = enif_make_string( 345 | env, olm_account_last_error(account), ERL_NIF_LATIN1); 346 | 347 | return enif_make_tuple2(env, error_atom, error_message); 348 | } 349 | 350 | ERL_NIF_TERM result_atom = enif_make_atom(env, "ok"); 351 | ERL_NIF_TERM msg = 352 | enif_make_string(env, "Successfully removed", ERL_NIF_LATIN1); 353 | 354 | return enif_make_tuple2(env, result_atom, msg); 355 | } 356 | 357 | // Sessions 358 | 359 | static ERL_NIF_TERM 360 | create_outbound_session(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 361 | { 362 | OlmAccount *account; 363 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 364 | 365 | ErlNifBinary peer_id_key; 366 | enif_inspect_binary(env, argv[1], &peer_id_key); 367 | 368 | ErlNifBinary peer_one_time_key; 369 | enif_inspect_binary(env, argv[2], &peer_one_time_key); 370 | 371 | // Allocate new session 372 | size_t session_size = olm_session_size(); 373 | OlmSession *memory = enif_alloc_resource(session_resource, session_size); 374 | OlmSession *session = olm_session(memory); 375 | 376 | size_t random_length = olm_create_outbound_session_random_length(session); 377 | char bytes[random_length]; 378 | 379 | size_t result = olm_create_outbound_session(session, 380 | account, 381 | peer_id_key.data, 382 | peer_id_key.size, 383 | peer_one_time_key.data, 384 | peer_one_time_key.size, 385 | bytes, 386 | random_length); 387 | 388 | if (result == olm_error()) { 389 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 390 | ERL_NIF_TERM error_message = enif_make_string( 391 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 392 | 393 | enif_release_resource(session); 394 | 395 | return enif_make_tuple2(env, error_atom, error_message); 396 | } 397 | 398 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 399 | ERL_NIF_TERM term = enif_make_resource(env, session); 400 | enif_release_resource(session); 401 | 402 | return enif_make_tuple2(env, ok_atom, term); 403 | } 404 | 405 | static ERL_NIF_TERM 406 | create_inbound_session(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 407 | { 408 | OlmAccount *account; 409 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 410 | 411 | ErlNifBinary cyphertext, cyphertext_input; 412 | enif_inspect_binary(env, argv[1], &cyphertext_input); 413 | enif_alloc_binary(cyphertext_input.size, &cyphertext); 414 | memcpy(cyphertext.data, cyphertext_input.data, cyphertext_input.size); 415 | 416 | // Allocate new session 417 | size_t session_size = olm_session_size(); 418 | OlmSession *memory = enif_alloc_resource(session_resource, session_size); 419 | OlmSession *session = olm_session(memory); 420 | 421 | size_t result = olm_create_inbound_session( 422 | session, account, cyphertext.data, cyphertext.size); 423 | 424 | if (result == olm_error()) { 425 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 426 | ERL_NIF_TERM error_message = enif_make_string( 427 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 428 | 429 | enif_release_resource(session); 430 | enif_release_binary(&cyphertext); 431 | 432 | return enif_make_tuple2(env, error_atom, error_message); 433 | } 434 | 435 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 436 | ERL_NIF_TERM term = enif_make_resource(env, session); 437 | 438 | enif_release_resource(session); 439 | enif_release_binary(&cyphertext); 440 | 441 | return enif_make_tuple2(env, ok_atom, term); 442 | } 443 | 444 | static ERL_NIF_TERM 445 | create_inbound_session_from(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 446 | { 447 | OlmAccount *account; 448 | enif_get_resource(env, argv[0], account_resource, (void **) &account); 449 | 450 | ErlNifBinary cyphertext, cyphertext_input; 451 | enif_inspect_binary(env, argv[1], &cyphertext_input); 452 | enif_alloc_binary(cyphertext_input.size, &cyphertext); 453 | memcpy(cyphertext.data, cyphertext_input.data, cyphertext_input.size); 454 | 455 | ErlNifBinary peer_id_key; 456 | enif_inspect_binary(env, argv[2], &peer_id_key); 457 | 458 | // Allocate new session 459 | size_t session_size = olm_session_size(); 460 | OlmSession *memory = enif_alloc_resource(session_resource, session_size); 461 | OlmSession *session = olm_session(memory); 462 | 463 | size_t result = olm_create_inbound_session_from(session, 464 | account, 465 | peer_id_key.data, 466 | peer_id_key.size, 467 | cyphertext.data, 468 | cyphertext.size); 469 | 470 | if (result == olm_error()) { 471 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 472 | ERL_NIF_TERM error_message = enif_make_string( 473 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 474 | 475 | enif_release_resource(session); 476 | enif_release_binary(&cyphertext); 477 | 478 | return enif_make_tuple2(env, error_atom, error_message); 479 | } 480 | 481 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 482 | ERL_NIF_TERM term = enif_make_resource(env, session); 483 | 484 | enif_release_resource(session); 485 | enif_release_binary(&cyphertext); 486 | 487 | return enif_make_tuple2(env, ok_atom, term); 488 | } 489 | 490 | static ERL_NIF_TERM 491 | session_id(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 492 | { 493 | OlmSession *session; 494 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 495 | 496 | ErlNifBinary id; 497 | size_t id_length = olm_session_id_length(session); 498 | enif_alloc_binary(id_length, &id); 499 | 500 | size_t result = olm_session_id(session, id.data, id.size); 501 | 502 | if (result == olm_error()) { 503 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 504 | ERL_NIF_TERM error_message = enif_make_string( 505 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 506 | 507 | enif_release_binary(&id); 508 | 509 | return enif_make_tuple2(env, error_atom, error_message); 510 | } 511 | 512 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 513 | ERL_NIF_TERM term = enif_make_binary(env, &id); 514 | 515 | return enif_make_tuple2(env, ok_atom, term); 516 | } 517 | 518 | static ERL_NIF_TERM 519 | match_inbound_session(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 520 | { 521 | OlmSession *session; 522 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 523 | 524 | ErlNifBinary cyphertext, cyphertext_input; 525 | enif_inspect_binary(env, argv[1], &cyphertext_input); 526 | enif_alloc_binary(cyphertext_input.size, &cyphertext); 527 | memcpy(cyphertext.data, cyphertext_input.data, cyphertext_input.size); 528 | 529 | size_t result = 530 | olm_matches_inbound_session(session, cyphertext.data, cyphertext.size); 531 | 532 | if (result == olm_error()) { 533 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 534 | ERL_NIF_TERM error_message = enif_make_string( 535 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 536 | 537 | enif_release_binary(&cyphertext); 538 | 539 | return enif_make_tuple2(env, error_atom, error_message); 540 | } 541 | 542 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 543 | ERL_NIF_TERM term = enif_make_ulong(env, result); 544 | 545 | enif_release_binary(&cyphertext); 546 | 547 | return enif_make_tuple2(env, ok_atom, term); 548 | } 549 | 550 | static ERL_NIF_TERM 551 | match_inbound_session_from(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 552 | { 553 | OlmSession *session; 554 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 555 | 556 | ErlNifBinary cyphertext, cyphertext_input; 557 | enif_inspect_binary(env, argv[1], &cyphertext_input); 558 | enif_alloc_binary(cyphertext_input.size, &cyphertext); 559 | memcpy(cyphertext.data, cyphertext_input.data, cyphertext_input.size); 560 | 561 | ErlNifBinary peer_id_key; 562 | enif_inspect_binary(env, argv[2], &peer_id_key); 563 | 564 | size_t result = olm_matches_inbound_session_from(session, 565 | peer_id_key.data, 566 | peer_id_key.size, 567 | cyphertext.data, 568 | cyphertext.size); 569 | 570 | if (result == olm_error()) { 571 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 572 | ERL_NIF_TERM error_message = enif_make_string( 573 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 574 | 575 | enif_release_binary(&cyphertext); 576 | 577 | return enif_make_tuple2(env, error_atom, error_message); 578 | } 579 | 580 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 581 | ERL_NIF_TERM term = enif_make_ulong(env, result); 582 | 583 | enif_release_binary(&cyphertext); 584 | 585 | return enif_make_tuple2(env, ok_atom, term); 586 | } 587 | 588 | static ERL_NIF_TERM 589 | pickle_session(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 590 | { 591 | OlmSession *session; 592 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 593 | 594 | ErlNifBinary key; 595 | enif_inspect_binary(env, argv[1], &key); 596 | 597 | ErlNifBinary pickled; 598 | size_t pickled_length = olm_pickle_session_length(session); 599 | enif_alloc_binary(pickled_length, &pickled); 600 | 601 | size_t result = olm_pickle_session( 602 | session, key.data, key.size, pickled.data, pickled.size); 603 | 604 | if (result == olm_error()) { 605 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 606 | ERL_NIF_TERM error_message = enif_make_string( 607 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 608 | 609 | enif_release_binary(&pickled); 610 | 611 | return enif_make_tuple2(env, error_atom, error_message); 612 | } 613 | 614 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 615 | ERL_NIF_TERM term = enif_make_binary(env, &pickled); 616 | 617 | return enif_make_tuple2(env, ok_atom, term); 618 | } 619 | 620 | static ERL_NIF_TERM 621 | unpickle_session(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 622 | { 623 | ErlNifBinary pickled, pickled_input; 624 | ErlNifBinary key; 625 | 626 | // Read args. 627 | enif_inspect_binary(env, argv[0], &pickled_input); 628 | enif_alloc_binary(pickled_input.size, &pickled); 629 | memcpy(pickled.data, pickled_input.data, pickled_input.size); 630 | 631 | enif_inspect_binary(env, argv[1], &key); 632 | 633 | // Alloc memory 634 | size_t session_size = olm_session_size(); 635 | OlmSession *memory = enif_alloc_resource(session_resource, session_size); 636 | OlmSession *session = olm_session(memory); 637 | 638 | size_t result = olm_unpickle_session( 639 | session, key.data, key.size, pickled.data, pickled.size); 640 | 641 | if (result == olm_error()) { 642 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 643 | ERL_NIF_TERM error_message = enif_make_string( 644 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 645 | 646 | enif_release_resource(session); 647 | enif_release_binary(&pickled); 648 | 649 | return enif_make_tuple2(env, error_atom, error_message); 650 | } 651 | 652 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 653 | ERL_NIF_TERM term = enif_make_resource(env, session); 654 | 655 | enif_release_resource(session); 656 | enif_release_binary(&pickled); 657 | 658 | return enif_make_tuple2(env, ok_atom, term); 659 | } 660 | 661 | static ERL_NIF_TERM 662 | encrypt_message_type(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 663 | { 664 | OlmSession *session; 665 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 666 | 667 | size_t result = olm_encrypt_message_type(session); 668 | 669 | if (result == olm_error()) { 670 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 671 | ERL_NIF_TERM error_message = enif_make_string( 672 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 673 | 674 | return enif_make_tuple2(env, error_atom, error_message); 675 | } 676 | 677 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 678 | ERL_NIF_TERM type = enif_make_ulong(env, result); 679 | 680 | return enif_make_tuple2(env, ok_atom, type); 681 | } 682 | 683 | static ERL_NIF_TERM 684 | encrypt_message(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 685 | { 686 | OlmSession *session; 687 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 688 | 689 | ErlNifBinary plaintext; 690 | enif_inspect_binary(env, argv[1], &plaintext); 691 | 692 | size_t random_length = olm_encrypt_random_length(session); 693 | char bytes[random_length]; 694 | 695 | ErlNifBinary message; 696 | size_t message_length = olm_encrypt_message_length(session, plaintext.size); 697 | enif_alloc_binary(message_length, &message); 698 | 699 | size_t result = olm_encrypt(session, 700 | plaintext.data, 701 | plaintext.size, 702 | bytes, 703 | random_length, 704 | message.data, 705 | message.size); 706 | 707 | if (result == olm_error()) { 708 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 709 | ERL_NIF_TERM error_message = enif_make_string( 710 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 711 | 712 | enif_release_binary(&message); 713 | 714 | return enif_make_tuple2(env, error_atom, error_message); 715 | } 716 | 717 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 718 | ERL_NIF_TERM term = enif_make_binary(env, &message); 719 | 720 | return enif_make_tuple2(env, ok_atom, term); 721 | } 722 | 723 | static ERL_NIF_TERM 724 | decrypt_message(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 725 | { 726 | OlmSession *session; 727 | enif_get_resource(env, argv[0], session_resource, (void **) &session); 728 | 729 | size_t type; 730 | enif_get_ulong(env, argv[1], &type); 731 | 732 | ErlNifBinary cyphertext, cyphertext_input; 733 | enif_inspect_binary(env, argv[2], &cyphertext_input); 734 | enif_alloc_binary(cyphertext_input.size, &cyphertext); 735 | memcpy(cyphertext.data, cyphertext_input.data, cyphertext_input.size); 736 | 737 | ErlNifBinary plaintext; 738 | size_t plaintext_size = olm_decrypt_max_plaintext_length( 739 | session, type, cyphertext_input.data, cyphertext_input.size); 740 | 741 | enif_alloc_binary(plaintext_size, &plaintext); 742 | 743 | size_t result = olm_decrypt(session, 744 | type, 745 | cyphertext.data, 746 | cyphertext.size, 747 | plaintext.data, 748 | plaintext.size); 749 | 750 | if (result == olm_error()) { 751 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 752 | ERL_NIF_TERM error_message = enif_make_string( 753 | env, olm_session_last_error(session), ERL_NIF_LATIN1); 754 | 755 | enif_release_binary(&plaintext); 756 | enif_release_binary(&cyphertext); 757 | 758 | return enif_make_tuple2(env, error_atom, error_message); 759 | } 760 | 761 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 762 | ERL_NIF_TERM term = enif_make_binary(env, &plaintext); 763 | 764 | enif_release_binary(&cyphertext); 765 | 766 | return enif_make_tuple2(env, ok_atom, term); 767 | } 768 | 769 | // Utility 770 | 771 | static ERL_NIF_TERM 772 | utility_sha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 773 | { 774 | // Args 775 | ErlNifBinary input; 776 | enif_inspect_binary(env, argv[0], &input); 777 | 778 | size_t utility_size = olm_utility_size(); 779 | OlmUtility *memory = enif_alloc(utility_size); 780 | OlmUtility *utility = olm_utility(memory); 781 | 782 | ErlNifBinary output; 783 | size_t output_length = olm_sha256_length(utility); 784 | enif_alloc_binary(output_length, &output); 785 | 786 | size_t result = 787 | olm_sha256(utility, input.data, input.size, output.data, output.size); 788 | 789 | if (result == olm_error()) { 790 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 791 | ERL_NIF_TERM error_message = enif_make_string( 792 | env, olm_utility_last_error(utility), ERL_NIF_LATIN1); 793 | 794 | enif_release_binary(&output); 795 | enif_free(memory); 796 | 797 | return enif_make_tuple2(env, error_atom, error_message); 798 | } 799 | 800 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 801 | ERL_NIF_TERM term = enif_make_binary(env, &output); 802 | 803 | enif_free(memory); 804 | 805 | return enif_make_tuple2(env, ok_atom, term); 806 | } 807 | 808 | static ERL_NIF_TERM 809 | utility_ed25519_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 810 | { 811 | ErlNifBinary key; 812 | ErlNifBinary message; 813 | ErlNifBinary signature, signature_input; 814 | 815 | enif_inspect_binary(env, argv[0], &key); 816 | enif_inspect_binary(env, argv[1], &message); 817 | 818 | enif_inspect_binary(env, argv[2], &signature_input); 819 | enif_alloc_binary(signature_input.size, &signature); 820 | memcpy(signature.data, signature_input.data, signature_input.size); 821 | 822 | size_t utility_size = olm_utility_size(); 823 | OlmUtility *memory = enif_alloc(utility_size); 824 | OlmUtility *utility = olm_utility(memory); 825 | 826 | size_t result = olm_ed25519_verify(utility, 827 | key.data, 828 | key.size, 829 | message.data, 830 | message.size, 831 | signature.data, 832 | signature.size); 833 | 834 | if (result == olm_error()) { 835 | ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); 836 | ERL_NIF_TERM error_message = enif_make_string( 837 | env, olm_utility_last_error(utility), ERL_NIF_LATIN1); 838 | 839 | enif_free(memory); 840 | enif_release_binary(&signature); 841 | 842 | return enif_make_tuple2(env, error_atom, error_message); 843 | } 844 | 845 | ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok"); 846 | ERL_NIF_TERM msg = 847 | enif_make_string(env, "Signature verified", ERL_NIF_LATIN1); 848 | 849 | enif_free(memory); 850 | enif_release_binary(&signature); 851 | 852 | return enif_make_tuple2(env, ok_atom, msg); 853 | } 854 | 855 | // Let's define the array of ErlNifFunc beforehand: 856 | static ErlNifFunc nif_funcs[] = { 857 | // {erl_function_name, erl_function_arity, c_function} 858 | {"version", 0, version}, 859 | {"create_account", 0, create_account}, 860 | {"pickle_account", 2, pickle_account}, 861 | {"unpickle_account", 2, unpickle_account}, 862 | {"account_identity_keys", 1, account_identity_keys}, 863 | {"account_sign", 2, account_sign}, 864 | {"account_one_time_keys", 1, account_one_time_keys}, 865 | {"account_mark_keys_as_published", 1, account_mark_keys_as_published}, 866 | {"account_max_one_time_keys", 1, account_max_one_time_keys}, 867 | {"account_generate_one_time_keys", 2, account_generate_one_time_keys}, 868 | {"remove_one_time_keys", 2, remove_one_time_keys}, 869 | {"create_outbound_session", 3, create_outbound_session}, 870 | {"create_inbound_session", 2, create_inbound_session}, 871 | {"create_inbound_session_from", 3, create_inbound_session_from}, 872 | {"session_id", 1, session_id}, 873 | {"match_inbound_session", 2, match_inbound_session}, 874 | {"match_inbound_session_from", 3, match_inbound_session_from}, 875 | {"pickle_session", 2, pickle_session}, 876 | {"unpickle_session", 2, unpickle_session}, 877 | {"encrypt_message_type", 1, encrypt_message_type}, 878 | {"encrypt_message", 2, encrypt_message}, 879 | {"decrypt_message", 3, decrypt_message}, 880 | {"utility_sha256", 1, utility_sha256}, 881 | {"utility_ed25519_verify", 3, utility_ed25519_verify}}; 882 | 883 | ERL_NIF_INIT(Elixir.Olm.NIF, nif_funcs, nif_load, NULL, NULL, NULL) 884 | -------------------------------------------------------------------------------- /lib/olm.ex: -------------------------------------------------------------------------------- 1 | defmodule Olm do 2 | @moduledoc """ 3 | Elixir/Erlang bindings to the olm and megolm cryptographic ratchets. 4 | """ 5 | 6 | alias Olm.NIF 7 | 8 | @doc """ 9 | The version number of the Olm C library. 10 | """ 11 | def version() do 12 | {major, minor, patch} = NIF.version() 13 | "#{major}.#{minor}.#{patch}" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/olm/account.ex: -------------------------------------------------------------------------------- 1 | defmodule Olm.Account do 2 | @moduledoc """ 3 | Functions for working with Olm Accounts. 4 | """ 5 | 6 | alias Olm.{NIF, NIFError} 7 | alias Jason 8 | 9 | @doc """ 10 | Creates a new account. 11 | """ 12 | def create() do 13 | case NIF.create_account() do 14 | {:ok, account_ref} -> account_ref 15 | {:error, error} -> raise NIFError, error 16 | end 17 | end 18 | 19 | @doc """ 20 | Stores an account as a base64 string. Encrypts the account using the supplied key. 21 | """ 22 | def pickle(account_ref, key) when is_reference(account_ref) and is_binary(key) do 23 | case NIF.pickle_account(account_ref, key) do 24 | {:ok, pickled_account} -> pickled_account 25 | {:error, error} -> raise NIFError, error 26 | end 27 | end 28 | 29 | @doc """ 30 | Loads an account from a pickled base64 string. Decrypts the account using the supplied key. 31 | """ 32 | def unpickle(pickled_account, key) when is_binary(pickled_account) and is_binary(key) do 33 | case NIF.unpickle_account(pickled_account, key) do 34 | {:ok, account_ref} -> 35 | {:ok, account_ref} 36 | 37 | {:error, 'BAD_ACCOUNT_KEY'} -> 38 | {:error, "bad account key: can't decrypt the pickled account"} 39 | 40 | {:error, error} -> 41 | raise NIFError, error 42 | end 43 | end 44 | 45 | @doc """ 46 | Returns the public parts of the identity keys for the account. 47 | """ 48 | def identity_keys(account_ref) when is_reference(account_ref) do 49 | case NIF.account_identity_keys(account_ref) do 50 | {:ok, keys_as_json} -> Jason.decode!(keys_as_json, keys: :atoms) 51 | {:error, error} -> raise NIFError, error 52 | end 53 | end 54 | 55 | @doc """ 56 | Signs a message with the ed25519 key for this account. 57 | """ 58 | def sign(account_ref, message) when is_reference(account_ref) and is_binary(message) do 59 | case NIF.account_sign(account_ref, message) do 60 | {:ok, signed} -> signed 61 | {:error, error} -> raise NIFError, error 62 | end 63 | end 64 | 65 | @doc """ 66 | Returns the public parts of the unpublished one time keys for the account. 67 | """ 68 | def one_time_keys(account_ref) when is_reference(account_ref) do 69 | case NIF.account_one_time_keys(account_ref) do 70 | {:ok, keys_as_json} -> Jason.decode!(keys_as_json, keys: :atoms) 71 | {:error, error} -> raise NIFError, error 72 | end 73 | end 74 | 75 | @doc """ 76 | Marks the current set of one time keys as being published. 77 | """ 78 | def mark_keys_as_published(account_ref) when is_reference(account_ref) do 79 | case NIF.account_mark_keys_as_published(account_ref) do 80 | {:ok, _} -> :ok 81 | {:error, error} -> raise NIFError, error 82 | end 83 | end 84 | 85 | @doc """ 86 | The largest number of one time keys this account can store. 87 | """ 88 | def max_one_time_keys(account_ref) when is_reference(account_ref) do 89 | case NIF.account_max_one_time_keys(account_ref) do 90 | {:ok, max} -> max 91 | {:error, error} -> raise NIFError, error 92 | end 93 | end 94 | 95 | @doc """ 96 | Generates a number of new one time keys. 97 | """ 98 | def generate_one_time_keys(account_ref, count, return \\ false) 99 | when is_reference(account_ref) and is_integer(count) and count > 0 do 100 | result = fn 101 | false -> :ok 102 | true -> one_time_keys(account_ref) 103 | end 104 | 105 | case NIF.account_generate_one_time_keys(account_ref, count) do 106 | {:ok, _} -> result.(return) 107 | {:error, error} -> raise NIFError, error 108 | end 109 | end 110 | 111 | @doc """ 112 | Removes the one time keys that the session used from the account. 113 | """ 114 | def remove_one_time_keys(account_ref, session_ref) 115 | when is_reference(account_ref) and is_reference(session_ref) do 116 | case NIF.remove_one_time_keys(account_ref, session_ref) do 117 | {:ok, _} -> :ok 118 | {:error, error} -> raise NIFError, error 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/olm/nif.ex: -------------------------------------------------------------------------------- 1 | defmodule Olm.NIFError do 2 | defexception [:message] 3 | 4 | @impl true 5 | def exception(value) do 6 | msg = "something is breaking in the C NIF, got: #{inspect(value)}" 7 | %Olm.NIFError{message: msg} 8 | end 9 | end 10 | 11 | defmodule Olm.NIF do 12 | @moduledoc false 13 | 14 | @on_load :load_nifs 15 | 16 | def load_nifs(), 17 | do: 18 | __DIR__ 19 | |> Path.join("../../priv/olm_nif") 20 | |> :erlang.load_nif(0) 21 | 22 | def version(), do: error(__ENV__.function()) 23 | 24 | def create_account(), do: error(__ENV__.function()) 25 | 26 | def pickle_account(_account_ref, _key), do: error(__ENV__.function()) 27 | 28 | def unpickle_account(_pickled_account, _key), do: error(__ENV__.function()) 29 | 30 | def account_identity_keys(_account_ref), do: error(__ENV__.function()) 31 | 32 | def account_sign(_account_ref, _message), do: error(__ENV__.function()) 33 | 34 | def account_one_time_keys(_account_ref), do: error(__ENV__.function()) 35 | 36 | def account_mark_keys_as_published(_account_ref), do: error(__ENV__.function()) 37 | 38 | def account_max_one_time_keys(_account_ref), do: error(__ENV__.function()) 39 | 40 | def account_generate_one_time_keys(_account_ref, _count), 41 | do: error(__ENV__.function()) 42 | 43 | def remove_one_time_keys(_account_ref, _session_ref), do: error(__ENV__.function()) 44 | 45 | def create_outbound_session(_account_ref, _peer_id_key, _peer_one_time_key), 46 | do: error(__ENV__.function()) 47 | 48 | def create_inbound_session(_account_ref, _message), do: error(__ENV__.function()) 49 | 50 | def create_inbound_session_from(_account_ref, _message, _peer_id_key), 51 | do: error(__ENV__.function()) 52 | 53 | def session_id(_session_ref), do: error(__ENV__.function()) 54 | 55 | def match_inbound_session(_session_ref, _message), do: error(__ENV__.function()) 56 | 57 | def match_inbound_session_from(_session_ref, _message, _peer_id_key), 58 | do: error(__ENV__.function()) 59 | 60 | def pickle_session(_session_ref, _key), do: error(__ENV__.function()) 61 | 62 | def unpickle_session(_pickled_session, _key), do: error(__ENV__.function()) 63 | 64 | def encrypt_message_type(_session_ref), do: error(__ENV__.function()) 65 | 66 | def encrypt_message(_session_ref, _plaintext), do: error(__ENV__.function()) 67 | 68 | def decrypt_message(_session_ref, _type, _cyphertext), do: error(__ENV__.function()) 69 | 70 | def utility_sha256(_string), do: error(__ENV__.function()) 71 | 72 | def utility_ed25519_verify(_key, _message, _signature), do: error(__ENV__.function()) 73 | 74 | defp error({function_name, arity}), 75 | do: :erlang.nif_error("NIF #{function_name}/#{arity} not implemented") 76 | end 77 | -------------------------------------------------------------------------------- /lib/olm/session.ex: -------------------------------------------------------------------------------- 1 | defmodule Olm.Session do 2 | @moduledoc """ 3 | Functions for working with Olm Sessions. 4 | """ 5 | 6 | alias Olm.{NIF, NIFError} 7 | 8 | @doc """ 9 | Creates a new out-bound session for sending messages to a given peer identity key and one time key. 10 | """ 11 | def new_outbound(account_ref, peer_id_key, peer_one_time_key) 12 | when is_reference(account_ref) and is_binary(peer_id_key) and is_binary(peer_one_time_key) do 13 | case NIF.create_outbound_session(account_ref, peer_id_key, peer_one_time_key) do 14 | {:ok, session_ref} -> session_ref 15 | {:error, error} -> raise NIFError, error 16 | end 17 | end 18 | 19 | @doc """ 20 | Creates a new in-bound session for sending/receiving messages from an incoming pre key message. 21 | """ 22 | def new_inbound(account_ref, message) when is_reference(account_ref) and is_binary(message) do 23 | case NIF.create_inbound_session(account_ref, message) do 24 | {:ok, session_ref} -> session_ref 25 | {:error, error} -> raise NIFError, error 26 | end 27 | end 28 | 29 | @doc """ 30 | Creates a new in-bound session for sending/receiving messages from an incoming pre key message and includes an identity key check. 31 | """ 32 | def new_inbound(account_ref, message, peer_id_key) 33 | when is_reference(account_ref) and is_binary(message) and is_binary(peer_id_key) do 34 | case NIF.create_inbound_session_from(account_ref, message, peer_id_key) do 35 | {:ok, session_ref} -> session_ref 36 | {:error, error} -> raise NIFError, error 37 | end 38 | end 39 | 40 | @doc """ 41 | An identifier for this session. 42 | 43 | Will be the same for both ends of the conversation. 44 | """ 45 | def id(session_ref) when is_reference(session_ref) do 46 | case NIF.session_id(session_ref) do 47 | {:ok, id} -> id 48 | {:error, error} -> raise NIFError, error 49 | end 50 | end 51 | 52 | @doc """ 53 | Checks if the pre key message is for this in-bound session. 54 | """ 55 | def match_inbound(session_ref, message) when is_reference(session_ref) and is_binary(message) do 56 | case NIF.match_inbound_session(session_ref, message) do 57 | {:ok, val} -> val 58 | {:error, error} -> raise NIFError, error 59 | end 60 | end 61 | 62 | @doc """ 63 | Checks if the pre key message is for this in-bound session and includes an identity key check. 64 | """ 65 | def match_inbound(session_ref, message, peer_id_key) 66 | when is_reference(session_ref) and is_binary(message) and is_binary(peer_id_key) do 67 | case NIF.match_inbound_session_from(session_ref, message, peer_id_key) do 68 | {:ok, val} -> val 69 | {:error, error} -> raise NIFError, error 70 | end 71 | end 72 | 73 | @doc """ 74 | Stores a session as a base64 string. 75 | """ 76 | def pickle(session_ref, key) when is_reference(session_ref) and is_binary(key) do 77 | case NIF.pickle_session(session_ref, key) do 78 | {:ok, pickled_session} -> pickled_session 79 | {:error, error} -> raise NIFError, error 80 | end 81 | end 82 | 83 | @doc """ 84 | Loads a session from a pickled base64 string. 85 | """ 86 | def unpickle(pickled_session, key) when is_binary(pickled_session) and is_binary(key) do 87 | case NIF.unpickle_session(pickled_session, key) do 88 | {:ok, session_ref} -> session_ref 89 | {:error, error} -> raise NIFError, error 90 | end 91 | end 92 | 93 | @doc """ 94 | Encrypts a message using the session. 95 | """ 96 | def encrypt_message(session_ref, plaintext) 97 | when is_reference(session_ref) and is_binary(plaintext) do 98 | type = 99 | case NIF.encrypt_message_type(session_ref) do 100 | {:ok, type} -> type 101 | {:error, error} -> raise NIFError, error 102 | end 103 | 104 | case NIF.encrypt_message(session_ref, plaintext) do 105 | {:ok, cyphertext} -> %{cyphertext: cyphertext, type: type} 106 | {:error, error} -> raise NIFError, error 107 | end 108 | end 109 | 110 | @doc """ 111 | Decrypts a message using the session. 112 | """ 113 | def decrypt_message(session_ref, type, cyphertext) 114 | when is_reference(session_ref) and is_integer(type) do 115 | case NIF.decrypt_message(session_ref, type, cyphertext) do 116 | {:ok, plaintext} -> 117 | plaintext 118 | |> String.chunk(:printable) 119 | |> List.first() 120 | 121 | {error, error} -> 122 | raise NIFError, error 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/olm/utility.ex: -------------------------------------------------------------------------------- 1 | defmodule Olm.Utility do 2 | @moduledoc """ 3 | Olm Utility functions. 4 | """ 5 | 6 | alias Olm.{NIF, NIFError} 7 | 8 | @doc """ 9 | Calculates the SHA-256 hash of the input and encodes it as base64. 10 | """ 11 | def sha256(to_hash) when is_binary(to_hash) do 12 | case NIF.utility_sha256(to_hash) do 13 | {:ok, hash} -> hash 14 | {:error, error} -> raise NIFError, error 15 | end 16 | end 17 | 18 | @doc """ 19 | Verifies an ed25519 signature. 20 | """ 21 | def verify_ed25519(key, message, signature) 22 | when is_binary(key) and is_binary(message) and is_binary(signature) do 23 | case NIF.utility_ed25519_verify(key, message, signature) do 24 | {:ok, _} -> {:ok, "verified"} 25 | {:error, 'BAD_MESSAGE_MAC'} -> {:error, "bad message MAC"} 26 | {:error, error} -> raise NIFError, error 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Olm.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :olm, 7 | version: "0.1.0-rc", 8 | elixir: "~> 1.10", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps(), 11 | compilers: [:olm_nifs] ++ Mix.compilers(), 12 | aliases: aliases(), 13 | description: description(), 14 | docs: docs(), 15 | package: package() 16 | ] 17 | end 18 | 19 | # Run "mix help compile.app" to learn about applications. 20 | def application do 21 | [ 22 | extra_applications: [:logger] 23 | ] 24 | end 25 | 26 | # Run "mix help deps" to learn about dependencies. 27 | defp deps do 28 | [ 29 | {:jason, "~> 1.4"}, 30 | {:ex_doc, "~> 0.29", only: :dev, runtime: false} 31 | ] 32 | end 33 | 34 | defp aliases do 35 | [fmt: ["format", "cmd clang-format -i c_src/*.[ch]"]] 36 | end 37 | 38 | defp description() do 39 | """ 40 | Elixir/Erlang NIF bindings for the olm and megolm cryptographic ratchets 41 | """ 42 | end 43 | 44 | defp docs do 45 | [ 46 | main: "readme", 47 | name: "Olm", 48 | source_url: "https://github.com/niklaslong/olm-elixir", 49 | extras: ["README.md"] 50 | ] 51 | end 52 | 53 | defp package() do 54 | [ 55 | maintainers: ["Niklas Long"], 56 | licenses: ["MIT"], 57 | links: %{"GitHub" => "https://github.com/niklaslong/olm-elixir"}, 58 | files: ~w(lib priv .formatter.exs mix.exs README* LICENSE* c_src Makefile .clang-format) 59 | ] 60 | end 61 | end 62 | 63 | defmodule Mix.Tasks.Compile.OlmNifs do 64 | def run(_args) do 65 | {result, _errcode} = System.cmd("make", [], stderr_to_stdout: true) 66 | IO.binwrite(result) 67 | end 68 | 69 | def clean do 70 | if File.exists?("priv/olm_nif.so") do 71 | File.rm!("priv/olm_nif.so") 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, 3 | "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [: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", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, 4 | "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, 5 | "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"}, 6 | "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"}, 7 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 8 | "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, 9 | } 10 | -------------------------------------------------------------------------------- /test/olm/account_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Olm.AccountTest do 2 | use ExUnit.Case 3 | alias Olm.{Account, Session} 4 | 5 | doctest Account 6 | 7 | defp create_account(_context), do: %{account: Account.create()} 8 | defp pickle_account(context), do: %{pickled_account: Account.pickle(context.account, "key")} 9 | 10 | describe "create/0:" do 11 | test "returns a reference to an account resource" do 12 | assert is_reference(Account.create()) 13 | end 14 | end 15 | 16 | describe "pickle/2:" do 17 | setup :create_account 18 | 19 | test "returns the pickled account as a base64 string", context do 20 | assert context.account |> Account.pickle("key") |> is_binary() 21 | end 22 | end 23 | 24 | describe "unpickle/2:" do 25 | setup [:create_account, :pickle_account] 26 | 27 | test "returns a reference to the unpickled account", context do 28 | assert {:ok, account} = Account.unpickle(context.pickled_account, "key") 29 | assert is_reference(account) 30 | end 31 | 32 | test "returns an error when wrong key is given to decrypt the account", context do 33 | assert {:error, msg} = Account.unpickle(context[:pickled_account], "wrong_key") 34 | assert msg == "bad account key: can't decrypt the pickled account" 35 | end 36 | end 37 | 38 | describe "identity_keys/1:" do 39 | setup :create_account 40 | 41 | test "returns a map containing the identity keys for an account", context do 42 | keys = Account.identity_keys(context.account) 43 | 44 | assert Map.has_key?(keys, :curve25519) 45 | assert Map.has_key?(keys, :ed25519) 46 | 47 | assert is_binary(keys.curve25519) 48 | assert is_binary(keys.ed25519) 49 | end 50 | end 51 | 52 | describe "sign/2:" do 53 | setup :create_account 54 | 55 | test "returns the message signed with the ed25519 key", context do 56 | assert context.account |> Account.sign("message") |> is_binary 57 | end 58 | end 59 | 60 | describe "one_time_keys/1:" do 61 | setup :create_account 62 | 63 | test "returns a map of unpublished one time keys for an account (empty)", context do 64 | assert Account.one_time_keys(context.account) == %{curve25519: %{}} 65 | end 66 | 67 | test "returns a map of unpublished one time keys for an account (non-empty)", context do 68 | :ok = Account.generate_one_time_keys(context.account, n = 2) 69 | keys = Account.one_time_keys(context.account) 70 | 71 | assert Map.has_key?(keys, :curve25519) 72 | assert keys.curve25519 |> Map.keys() |> length() == n 73 | assert keys.curve25519 |> Map.values() |> Enum.each(&is_binary/1) 74 | end 75 | end 76 | 77 | describe "mark_keys_as_published/1:" do 78 | setup :create_account 79 | 80 | test "returns :ok after marking keys as published", context do 81 | assert Account.mark_keys_as_published(context.account) == :ok 82 | end 83 | end 84 | 85 | describe "max_one_time_keys/1:" do 86 | setup :create_account 87 | 88 | test "returns max number of one time keys for an account", context do 89 | assert context.account |> Account.max_one_time_keys() |> is_integer() 90 | end 91 | end 92 | 93 | describe "generate_one_time_keys/2" do 94 | setup :create_account 95 | 96 | test "returns :ok after generating accounts", context do 97 | assert Account.generate_one_time_keys(context.account, 1) == :ok 98 | end 99 | 100 | test "returns one time keys if return is set to true", context do 101 | assert context.account |> Account.generate_one_time_keys(3, true) |> is_map 102 | end 103 | end 104 | 105 | describe "remove_one_time_keys/2:" do 106 | setup :create_account 107 | 108 | test "returns :ok after removing used one time keys", context do 109 | peer_account = Account.create() 110 | 111 | id_key = 112 | peer_account 113 | |> Account.identity_keys() 114 | |> Map.get(:curve25519) 115 | 116 | one_time_key = 117 | peer_account 118 | |> Account.generate_one_time_keys(1, true) 119 | |> get_in([:curve25519, :AAAAAQ]) 120 | 121 | outbound_session = Session.new_outbound(context.account, id_key, one_time_key) 122 | pre_key_msg = Session.encrypt_message(outbound_session, "message") 123 | inbound_session = Session.new_inbound(peer_account, pre_key_msg.cyphertext) 124 | 125 | assert :ok = Account.remove_one_time_keys(peer_account, inbound_session) 126 | assert Account.one_time_keys(peer_account) == %{curve25519: %{}} 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /test/olm/session_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Olm.SessionTest do 2 | use ExUnit.Case 3 | alias Olm.{Session, Account} 4 | 5 | doctest Session 6 | 7 | ExUnit.Case.register_attribute(__MODULE__, :fixtures, accumulate: true) 8 | 9 | defp create_account(_context) do 10 | {account, id_key, one_time_key} = Account.create() |> generate_keys() 11 | %{id_key: id_key, one_time_key: one_time_key, account: account} 12 | end 13 | 14 | defp create_peer_account(_context) do 15 | {account, id_key, one_time_key} = Account.create() |> generate_keys() 16 | %{peer_id_key: id_key, peer_one_time_key: one_time_key, peer_account: account} 17 | end 18 | 19 | defp generate_keys(account) do 20 | id_key = 21 | account 22 | |> Account.identity_keys() 23 | |> Map.get(:curve25519) 24 | 25 | one_time_key = 26 | account 27 | |> Account.generate_one_time_keys(1, true) 28 | |> get_in([:curve25519, :AAAAAQ]) 29 | 30 | {account, id_key, one_time_key} 31 | end 32 | 33 | defp create_outbound_session(context), 34 | do: %{ 35 | outbound_session: 36 | Session.new_outbound(context.account, context.peer_id_key, context.peer_one_time_key) 37 | } 38 | 39 | defp pickle_session(context), 40 | do: %{pickled_session: Session.pickle(context.outbound_session, "key")} 41 | 42 | defp encrypt_message(context) do 43 | [%{msg_content: msg_content}] = context.registered.fixtures 44 | %{pre_key_msg: Session.encrypt_message(context.outbound_session, msg_content)} 45 | end 46 | 47 | defp create_inbound_session(context) do 48 | inbound_session = 49 | Session.new_inbound(context.peer_account, context.pre_key_msg.cyphertext, context.id_key) 50 | 51 | %{inbound_session: inbound_session} 52 | end 53 | 54 | describe "new_outbound/3:" do 55 | setup [:create_account, :create_peer_account] 56 | 57 | test "returns a reference to an outbound session", context do 58 | assert is_reference( 59 | Session.new_outbound( 60 | context.account, 61 | context.peer_id_key, 62 | context.peer_one_time_key 63 | ) 64 | ) 65 | end 66 | end 67 | 68 | describe "new_inbound/3" do 69 | setup [:create_account, :create_peer_account, :create_outbound_session] 70 | 71 | test "returns a session which can be used to decrypt messages (with id key)", context do 72 | pre_key_msg = Session.encrypt_message(context.outbound_session, "This is a message") 73 | 74 | inbound_session = 75 | Session.new_inbound(context.peer_account, pre_key_msg.cyphertext, context.id_key) 76 | 77 | assert is_reference(inbound_session) 78 | end 79 | 80 | test "returns a session which can be used to decrypt messages (without id key)", context do 81 | pre_key_msg = Session.encrypt_message(context.outbound_session, "This is a message") 82 | inbound_session = Session.new_inbound(context.peer_account, pre_key_msg.cyphertext) 83 | assert is_reference(inbound_session) 84 | end 85 | end 86 | 87 | describe "id/1:" do 88 | setup [ 89 | :create_account, 90 | :create_peer_account, 91 | :create_outbound_session, 92 | :encrypt_message, 93 | :create_inbound_session 94 | ] 95 | 96 | @fixtures %{msg_content: "This is a message"} 97 | test "returns session id", context do 98 | outbound_id = Session.id(context.outbound_session) 99 | inbound_id = Session.id(context.inbound_session) 100 | 101 | assert is_binary(outbound_id) 102 | assert outbound_id == inbound_id 103 | end 104 | end 105 | 106 | describe "match_inbound/3" do 107 | setup [ 108 | :create_account, 109 | :create_peer_account, 110 | :create_outbound_session, 111 | :encrypt_message, 112 | :create_inbound_session 113 | ] 114 | 115 | @fixtures %{msg_content: "This is a message"} 116 | test "returns 1 if current inbound session matches pre key message (with id key verification)", 117 | context do 118 | assert Session.match_inbound( 119 | context.inbound_session, 120 | context.pre_key_msg.cyphertext, 121 | context.id_key 122 | ) === 1 123 | end 124 | 125 | @fixtures %{msg_content: "This is a message"} 126 | test "returns 1 if current inbound session matches pre key message (without id key verification)", 127 | context do 128 | assert Session.match_inbound(context.inbound_session, context.pre_key_msg.cyphertext) === 1 129 | end 130 | end 131 | 132 | describe "pickle_session/2:" do 133 | setup [:create_account, :create_peer_account, :create_outbound_session, :pickle_session] 134 | 135 | test "returns the pickled session as a base64 string", context do 136 | assert is_binary(Session.pickle(context.outbound_session, "key")) 137 | end 138 | 139 | test "returns a reference to the unpickle session", context do 140 | assert is_reference(Session.unpickle(context.pickled_session, "key")) 141 | end 142 | end 143 | 144 | describe "encrypt_message/2:" do 145 | setup [:create_account, :create_peer_account, :create_outbound_session] 146 | 147 | test "returns base64 encoded cyphertext (pre-key)", context do 148 | message = Session.encrypt_message(context.outbound_session, "message") 149 | assert is_binary(message.cyphertext) 150 | assert message.type === 0 151 | end 152 | end 153 | 154 | describe "decrypt_message/3" do 155 | setup [ 156 | :create_account, 157 | :create_peer_account, 158 | :create_outbound_session, 159 | :encrypt_message, 160 | :create_inbound_session 161 | ] 162 | 163 | @fixtures %{msg_content: "This is a message"} 164 | test "returns the decrypted message", context do 165 | assert Session.decrypt_message( 166 | context.inbound_session, 167 | context.pre_key_msg.type, 168 | context.pre_key_msg.cyphertext 169 | ) == "This is a message" 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /test/olm/utility_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Olm.UtilityTest do 2 | use ExUnit.Case 3 | alias Olm.{Utility, Account} 4 | 5 | doctest Utility 6 | 7 | defp create_account(_context), do: %{account: Account.create()} 8 | defp identity_keys(context), do: %{identity_keys: Account.identity_keys(context.account)} 9 | defp sign(context), do: %{signature: Account.sign(context.account, "test")} 10 | 11 | describe "sha256/1:" do 12 | test "returns a hash of the input string" do 13 | assert "input" |> Utility.sha256() |> is_binary 14 | end 15 | end 16 | 17 | describe "verify_ed25519/3:" do 18 | setup [:create_account, :identity_keys, :sign] 19 | 20 | test "verifies signature", context do 21 | {:ok, msg} = 22 | Utility.verify_ed25519(context.identity_keys.ed25519, "test", context.signature) 23 | 24 | assert msg == "verified" 25 | end 26 | 27 | test "returns error for bad signature", context do 28 | {:error, msg} = 29 | Utility.verify_ed25519(context.identity_keys.ed25519, "bad_msg", context.signature) 30 | 31 | assert msg == "bad message MAC" 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/olm_test.exs: -------------------------------------------------------------------------------- 1 | defmodule OlmTest do 2 | use ExUnit.Case 3 | 4 | doctest Olm 5 | 6 | test "version/0" do 7 | assert String.first(Olm.version()) == "3" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------