From 050e6e44a524e5ac6b33476e0ce7d4cbb8f2a200 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 6 Sep 2021 15:06:51 -0400 Subject: [PATCH] Add mix task for decrypting uploads --- lib/mix/tasks/wiki/upload.ex | 23 +++++++++++++++++++++++ lib/wiki/content/upload.ex | 23 +++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 lib/mix/tasks/wiki/upload.ex diff --git a/lib/mix/tasks/wiki/upload.ex b/lib/mix/tasks/wiki/upload.ex new file mode 100644 index 0000000..317acc9 --- /dev/null +++ b/lib/mix/tasks/wiki/upload.ex @@ -0,0 +1,23 @@ +defmodule Mix.Tasks.Wiki.Upload do + use Mix.Task + alias Wiki.{Repo, Accounts, Accounts.User, Content.Upload} + + @shortdoc "Decrypts an upload into the given file" + def run(["decrypt", id, output_file]) do + email = IO.gets("Email: ") |> String.trim() + password = IO.gets("Password: ") |> String.trim() + + Mix.Task.run("app.start") + + user = Repo.get_by!(User, email: email) + upload = Repo.get!(Upload, String.to_integer(id)) |> Repo.preload(:page) + + if upload.page.user_id != user.id do + raise "upload owner does not match provided user" + else + key = Accounts.generate_content_encryption_key(user, %{"password" => password}) + key = Base.decode16!(key, case: :lower) + Upload.decrypt_to_file(upload, key, output_file) + end + end +end diff --git a/lib/wiki/content/upload.ex b/lib/wiki/content/upload.ex index cfdb61b..0ae5fb7 100644 --- a/lib/wiki/content/upload.ex +++ b/lib/wiki/content/upload.ex @@ -80,7 +80,7 @@ defmodule Wiki.Content.Upload do end @spec upload_dir() :: String.t() - defp upload_dir() do + def upload_dir() do dir = Application.get_env(:wiki, :upload_dir) if is_nil(dir) do @@ -98,6 +98,25 @@ defmodule Wiki.Content.Upload do :crypto.crypto_one_time(:aes_256_ctr, key, iv, encrypted_data, false) end + @spec decrypt_to_file(t(), binary(), String.t()) :: File.t() + def decrypt_to_file(upload, key, path) when is_binary(path) do + {:ok, output} = File.open(path, [:write, :binary]) + + :ok = decrypt_to_file(upload, key, output) + + File.close(output) + end + + @spec decrypt_to_file(t(), binary(), File.io_device()) :: File.t() + def decrypt_to_file(upload, key, output) do + decrypt_stream(upload, key) + |> Enum.each(fn data -> + IO.binwrite(output, data) + end) + + :ok + end + @spec decrypt_stream(t(), binary()) :: Enumerable.t() def decrypt_stream(%__MODULE__{relative_path: filename, encryption_iv: iv}, key) do start_fun = fn -> @@ -125,7 +144,7 @@ defmodule Wiki.Content.Upload do end end - after_fun = fn {state, input_dev} -> + after_fun = fn {_state, input_dev} -> File.close(input_dev) end