From e1b004dbc4bda156f049394790598cd7e056559e Mon Sep 17 00:00:00 2001 From: nixo Date: Mon, 6 Jan 2020 14:20:08 +0100 Subject: [PATCH] First version. Almost everything is implemented. Missing library Right now I'm hadrcoding guix path for the library. Should find out how to use BinaryBuilder --- Manifest.toml | 59 +++++++++++++ Project.toml | 8 ++ src/Olm.jl | 39 +++++++++ src/memory.jl | 13 +++ src/wrapper.jl | 220 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 339 insertions(+) create mode 100644 Manifest.toml create mode 100644 Project.toml create mode 100644 src/Olm.jl create mode 100644 src/memory.jl create mode 100644 src/wrapper.jl diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 0000000..f0850d6 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,59 @@ +# This file is machine-generated - editing it directly is not advised + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.0" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates", "Test"] +git-tree-sha1 = "0139ba59ce9bc680e2925aec5b7db79065d60556" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "0.3.10" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[Test]] +deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..b3fa4cc --- /dev/null +++ b/Project.toml @@ -0,0 +1,8 @@ +name = "Olm" +uuid = "35e2252f-68e6-415a-955a-e84ed199fea9" +authors = ["nixo "] +version = "0.1.0" + +[deps] +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/src/Olm.jl b/src/Olm.jl new file mode 100644 index 0000000..bb6b52d --- /dev/null +++ b/src/Olm.jl @@ -0,0 +1,39 @@ +module Olm # include("olm.jl") +using Random +using JSON + +const libolm = "/gnu/store/ks2szjc82r1wqaxdq4vsy6sq9plwd4fs-libolm-3.1.4/lib/libolm.so" +const error = ccall((:olm_error, libolm), Csize_t, ()) + +struct OlmAccount + ptr::Ptr{Cvoid} + # Should I store it? + # memory::Vector{UInt} +end + +struct OlmSession + ptr::Ptr{Cvoid} +end + +struct OlmUtility + ptr::Ptr{Cvoid} +end + +include("memory.jl") +include("wrapper.jl") + +# # Save/Load account! +# a = OlmAccount() +# generate_one_time_keys(a, 10) +# # deleted after use +# enc_key = "pass" |> collect +# dec_key = deepcopy(enc_key) +# enc_key = dec_key = Char[] +# p = pickle!(a, enc_key) +# write("account.bin", p) +# d = read("account.bin", String) |> collect .|> UInt8 +# @assert d == p +# n = OlmAccount(d, dec_key) +# o = OlmAccount(p, dec_key) + +end # Olm diff --git a/src/memory.jl b/src/memory.jl new file mode 100644 index 0000000..c5790f9 --- /dev/null +++ b/src/memory.jl @@ -0,0 +1,13 @@ +"Allocate an UInt8 array of length size, all zeros" +allocate(size) = zeros(UInt8, size) +const SRND = Random.RandomDevice() +"Allocate a criptographycally secure random UInt8 array of length size" +rallocate(size) = rand(SRND, UInt8, size) + +"""Replace all elements of an array with zeros. A custom function accepting +"type" may be passed as FUNC argument used instead of ZERO""" +function erase!(array::Array{T}; func = zero) where T + for i in 1:length(array) + array[i] = func(T) + end +end diff --git a/src/wrapper.jl b/src/wrapper.jl new file mode 100644 index 0000000..3768c98 --- /dev/null +++ b/src/wrapper.jl @@ -0,0 +1,220 @@ +"The size of an account object in bytes" +account_size() = ccall((:olm_account_size, libolm), Csize_t, ()) + +"""Initialise an account object using the supplied memory +The supplied memory must be at least olm_account_size() bytes""" +function account() + memory = allocate(account_size()) + ccall((:olm_account, libolm), Ptr{Cvoid}, (Ptr{Cvoid},), memory) +end + +"The number of random bytes needed to create an account." +create_account_random_length(a::OlmAccount) = + ccall((:olm_create_account_random_length, libolm), Csize_t, + (Ptr{Cvoid},), a.ptr) + +"""Creates a new account. Returns olm_error() on failure. If there weren't +enough random bytes then olm_account_last_error() will be +NOT_ENOUGH_RANDOM""" +function create(a::OlmAccount) + random_length = create_account_random_length(a) + random = allocate(random_length) + res = ccall((:olm_create_account, libolm), Csize_t, + (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), + a.ptr, random, random_length) + return res != error +end + +function OlmAccount() + a = OlmAccount(account()) + create(a) + a +end + +"""Loads an account from a pickled base64 string. Decrypts the account using the +supplied key. Returns olm_error() on failure. If the key doesn't match the one +used to encrypt the account then olm_account_last_error() will be +"BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then +olm_account_last_error() will be "INVALID_BASE64". The input pickled buffer is +destroyed""" +function unpickle!(a::OlmAccount, pickle::Vector{Char}, passphrase::Vector{Char}) + memlength = pickle_length(a) + res = ccall((:olm_unpickle_account, libolm), Csize_t, + (Ptr{Cvoid}, + Ptr{Cvoid}, Csize_t, + Ptr{Cvoid}, Csize_t,), + a.ptr, + passphrase, length(passphrase), + collect(pickle), length(pickle)) + # If passphrase is empty, pickle is not encrypted, delete it. + # Else, deleting the key is fine + erase!(length(passphrase) == 0 ? passphrase : pickle, func = rand) + if res == error + throw(last_error(a)) + else + a + end +end + +"""Initialize a pickled account. Note htat passphrase is cleared after use. +""" +function OlmAccount(pickle::Vector{Char}, passphrase::Vector{Char}) + a = OlmAccount(account()) + unpickle!(a, pickle, passphrase) +end + +"""A null terminated string describing the most recent error to happen to an +account""" +last_error(a::OlmAccount) = + ccall((:olm_account_last_error, libolm), Cstring, (Ptr{Cvoid},), a.ptr) |> + unsafe_string + +"""A null terminated string describing the most recent error to happen to a +session""" +last_error(s::OlmSession) = + ccall((:olm_session_last_error, libolm), Cstring, (Ptr{Cvoid},), s.ptr) |> + unsafe_string + +"Returns the number of bytes needed to store an account" +pickle_length(a::OlmAccount) = + ccall((:olm_pickle_account_length, libolm), Csize_t, (Ptr{Cvoid},), a.ptr) + +"Returns the number of bytes needed to store a session" +pickle_length(s::OlmSession) = + ccall((:olm_pickle_session_length, libolm), Csize_t, (Ptr{Cvoid},), s.ptr) + +"""Stores an account as a base64 string. Encrypts the account using the +supplied key. Returns the length of the pickled account on success. +Returns olm_error() on failure. If the pickle output buffer +is smaller than olm_pickle_account_length() then olm_account_last_error() +will be "OUTPUT_BUFFER_TOO_SMALL".""" +function pickle!(a::OlmAccount, passphrase::Vector{Char}) + memlength = pickle_length(a) + memory = allocate(memlength) + res = ccall((:olm_pickle_account, libolm), Csize_t, + (Ptr{Cvoid}, + Ptr{Cvoid}, Csize_t, + Ptr{Cvoid}, Csize_t,), + a.ptr, + passphrase, length(passphrase), + memory, memlength) + # zero(Char) does not extists, but rand does + erase!(passphrase, func = rand) + # Check results + if res == memlength + memory + else + throw(last_error(a)) + end +end +pickle!(a::OlmAccount) = pickle!(a, Char[]) + +# Base.getpass("Account encryption key") + +"The size of the output buffer needed to hold the identity keys" +account_identity_keys_length(a::OlmAccount) = + ccall((:olm_account_identity_keys_length, libolm), Csize_t, + (Ptr{Cvoid},), a.ptr) + +"The largest number of one time keys this account can store." +max_number_of_one_time_keys() = + ccall((:olm_account_max_number_of_one_time_keys, libolm), Csize_t, ()) + +"""The number of random bytes needed to generate a given number of new one time +keys.""" +function generate_one_time_keys_random_length(a::OlmAccount, count::Integer) + ccall((:olm_account_generate_one_time_keys_random_length, libolm), Csize_t, + (Ptr{Cvoid}, Csize_t), + a.ptr, count) +end + +"""Marks the current set of one time keys as being published.""" +mark_keys_as_published(a::OlmAccount) = + ccall((:olm_account_mark_keys_as_published, libolm), Csize_t, + (Ptr{Cvoid},), a.ptr) + +"The length of an ed25519 signature encoded as base64." +signature_length(a::OlmAccount) = + ccall((:olm_account_signature_length, libolm), Csize_t, + (Ptr{Cvoid},), a.ptr) + +"The size of the output buffer needed to hold the one time keys" +one_time_keys_length(a::OlmAccount) = + ccall((:olm_account_one_time_keys_length, libolm), Csize_t, + (Ptr{Cvoid},), a.ptr) + +"""Writes the public parts of the identity keys for the account into the +identity_keys output buffer. Returns olm_error() on failure. If the +identity_keys buffer was too small then olm_account_last_error() will be +OUTPUT_BUFFER_TOO_SMALL.""" +function identity_keys(a::OlmAccount) + identity_key_length = identity_keys_length(a) + identity_keys = allocate(identity_key_length) + res = ccall((:olm_account_identity_keys, libolm), Csize_t, + (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), + a.ptr, identity_keys, identity_key_length) + identity_keys |> String +end + +"""Signs a message with the ed25519 key for this account. Returns olm_error() on +failure. If the signature buffer was too small then olm_account_last_error() +will be OUTPUT_BUFFER_TOO_SMALL""" +function sign!(a::OlmAccount, message) + siglength = signature_length(a) + output = allocate(siglength) + ccall((:olm_account_sign, libolm), Csize_t, + (Ptr{Cvoid}, + Ptr{Cvoid}, Csize_t, + Ptr{Cvoid}, Csize_t), + a.ptr, + message, length(message), + output, siglength) + erase!(message) + String(deepcopy(output)) +end + +"""Generates a number of new one time keys. If the total number of keys stored +by this account exceeds max_number_of_one_time_keys() then the old keys are +discarded. Returns olm_error() on error. If the number of random bytes is +too small then olm_account_last_error() will be NOT_ENOUGH_RANDOM.""" +function generate_one_time_keys(a::OlmAccount, count::Integer) + 0 < count <= max_number_of_one_time_keys() || + throw("Error: invalid number of keys") + len = generate_one_time_keys_random_length(a, count) + random = rallocate(len) + res = ccall((:olm_account_generate_one_time_keys, libolm), Csize_t, + (Ptr{Cvoid}, Csize_t, Ptr{Cvoid}, Csize_t), + a.ptr, count, random, len) + erase!(random) + Int(res) +end + +"""Writes the public parts of the unpublished one time keys for the account into +the one_time_keys output buffer. +

+The returned data is a JSON-formatted object with the single property +curve25519, which is itself an object mapping key id to +base64-encoded Curve25519 key. For example: +

+{
+  curve25519: {
+     "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo",
+     "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU"
+  }
+}
+
+Returns olm_error() on failure. +

+If the one_time_keys buffer was too small then olm_account_last_error() +will be "OUTPUT_BUFFER_TOO_SMALL".""" +function one_time_keys(a::OlmAccount) + one_time_keys_len = one_time_keys_length(a) + one_time_keys = allocate(one_time_keys_len) + res = ccall((:olm_account_one_time_keys, libolm), Csize_t, + (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), + a.ptr, one_time_keys, one_time_keys_len) + if res == error + throw(last_error(a)) + end + JSON.parse(String(deepcopy(one_time_keys))) +end