defmodule Shard.Keys do @moduledoc""" Module for saving private keys, signing messages and checking message signatures. """ use Agent require Logger @key_db [Application.get_env(:shard, :data_path), "key_db"] |> Path.join |> String.to_atom def start_link(_) do Agent.start_link(__MODULE__, :init, [], name: __MODULE__) end def init() do :dets.start {:ok, @key_db} = :dets.open_file(@key_db, [type: :set]) for [pk, _] <- :dets.match(@key_db, {:'$1', :'$2'}) do m = %SApp.Identity.Manifest{pk: pk} Shard.Manager.find_or_start m GenServer.cast(Shard.Manager, {:pin, SData.term_hash(m)}) end nil end defp gen_keypair(suffix, n \\ 0) do %{public: pk, secret: sk} = :enacl.sign_keypair if rem(n, 10000) == 0 do Logger.info "#{n}... expected #{:math.pow(256, byte_size(suffix))}" end if check_suffix(pk, suffix) do {pk, sk} else gen_keypair(suffix, n+1) end end defp check_suffix(pk, suffix) do :binary.longest_common_suffix([pk, suffix]) == byte_size(suffix) end @doc""" Return any public key for which we have the secret key. Generates a new keypair if necessary. """ def get_any_identity() do Agent.get(__MODULE__, fn _ -> case list_identities() do [x|_] -> x [] -> new_identity() end end) end @doc""" Generate a new keypair for a user identity, and start an Identity Shard for it. """ def new_identity() do {pk, sk} = gen_keypair(Application.get_env(:shard, :identity_suffix)) Logger.info "New identity: #{pk|>Base.encode16}" :dets.insert @key_db, {pk, sk} m = %SApp.Identity.Manifest{pk: pk} Shard.Manager.find_or_start m GenServer.cast(Shard.Manager, {:pin, SData.term_hash(m)}) pk end @doc""" List the public keys of all identities for which we have a secret key """ def list_identities() do for [pk, _sk] <- :dets.match(@key_db, {:"$1", :"$2"}), do: pk end @doc""" Lookup the secret key for a pk and sign a message with it. Returns the input value alongside its signature. Answer is {:ok, signed} if it worked, or :not_found if we didn't find the key. """ def sign(pk, bin) do case :dets.lookup @key_db, pk do [{^pk, sk}] -> {:ok, :enacl.sign(bin, sk)} _ -> {:error, :not_found} end end @doc""" Checks the signature appended to a signed message corresponds to a public key. If correct, returns {:ok, original_message} """ def open(pk, signed) do if valid_identity_pk? pk do :enacl.sign_open(signed, pk) else {:error, :invalid_pk_suffix} end end @doc""" Check if we have the secret key associated with a public key. """ def have_sk?(pk) do case :dets.lookup @key_db, pk do [{^pk, _sk}] -> true _ -> false end end @doc""" Return the secret key associated with a public key if we have it or `nil` otherwise. """ def get_sk(pk) do case :dets.lookup @key_db, pk do [{^pk, sk}] -> sk _ -> nil end end @doc""" Lookup the secret key for a pk and generate a detached signature for a message. The original message is not returned. Answer is {:ok, signature} if it worked, or :not_found if we don't have the corresponding secret key. """ def sign_detached(pk, bin) do case :dets.lookup @key_db, pk do [{^pk, sk}] -> {:ok, :enacl.sign_detached(bin, sk)} _ -> {:error, :not_found} end end @doc""" Verify a detached signature for a message Returns :ok if the signature was correct. """ def verify(pk, bin, sign) do if valid_identity_pk? pk do case :enacl.sign_verify_detached(sign, bin, pk) do {:ok, _} -> :ok err -> err end else {:error, :invalid_pk_suffix} end end @doc""" Check if a public key is a valid identity pk. Requirement: have the correct suffix. """ def valid_identity_pk?(pk) do check_suffix(pk, Application.get_env(:shard, :identity_suffix)) end @doc""" Creates a displayable representation of a public key by taking the hex representation of its first four bytes. (not tamper proof but better than nothing) """ def pk_display(pk) do pk |> binary_part(0, 4) |> Base.encode16 |> String.downcase end end