lib/cryppo/encrypted_data_with_derived_key.ex

defmodule Cryppo.EncryptedDataWithDerivedKey do
  @moduledoc """
  A struct for a derived key and data encrypted with this derived key
  """

  import Cryppo.Base64
  import Cryppo.Strategies, only: [find_key_derivation_strategy: 1]
  alias Cryppo.{DerivedKey, EncryptedData, EncryptedDataWithDerivedKey, Serialization}

  @typedoc """
  Struct `Cryppo.EncryptedData`

  A `Cryppo.EncryptedData` struct contains

  * `encrypted_data`: a `Cryppo.EncryptedData` struct
  * `derived_key`: a `Cryppo.DerivedKey` struct
  """

  @type t :: %__MODULE__{
          encrypted_data: EncryptedData.t(),
          derived_key: DerivedKey.t()
        }

  @enforce_keys [:encrypted_data, :derived_key]
  defstruct [:encrypted_data, :derived_key]

  @doc false
  @spec load(String.t(), String.t(), String.t(), String.t(), String.t()) ::
          {:ok, t()}
          | {:error,
             :invalid_base64
             | :invalid_derivation_artefacts
             | :invalid_bson
             | String.t()}
          | {:unsupported_encryption_strategy, binary}
          | {:unsupported_key_derivation_strategy, binary}
  def load(
        strategy_name,
        encrypted_data_base64,
        encryption_artefacts_base64,
        key_derivation_strategy,
        derivation_artefacts_base64
      ) do
    with {:ok, key_derivation_mod} <-
           find_key_derivation_strategy(key_derivation_strategy),
         {:ok, derivation_artefacts} <- decode_base64(derivation_artefacts_base64),
         {:ok, salt, iterations, length} <- DerivedKey.load_artefacts(derivation_artefacts),
         {:ok, encrypted_data} <-
           EncryptedData.load(strategy_name, encrypted_data_base64, encryption_artefacts_base64) do
      hash = apply(key_derivation_mod, :hash_function, [])

      derived_key = %DerivedKey{
        encryption_key: nil,
        key_derivation_strategy: key_derivation_mod,
        salt: salt,
        iter: iterations,
        length: length,
        hash: hash
      }

      key = %__MODULE__{encrypted_data: encrypted_data, derived_key: derived_key}
      {:ok, key}
    end
  end

  defimpl Serialization do
    @spec serialize(EncryptedDataWithDerivedKey.t()) :: binary
    def serialize(%EncryptedDataWithDerivedKey{
          derived_key: %DerivedKey{} = derived_key,
          encrypted_data: %EncryptedData{} = encrypted_data
        }) do
      [encrypted_data, derived_key] |> Enum.map_join(".", &Serialization.serialize/1)
    end
  end
end