defmodule Shard.Keys do @moduledoc""" Module for saving private keys. """ 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]) 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 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} Shard.Manifest.start %SApp.Identity.Manifest{pk: pk} 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}] -> :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 def have_sk?(pk) do case :dets.lookup @key_db, pk do [{^pk, _sk}] -> true _ -> false end end 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 didn't find the key. """ def sign_detached(pk, bin) do case :dets.lookup @key_db, pk do [{^pk, sk}] -> :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 :enacl.sign_verify_detached(sign, bin, pk) 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 def pk_display(pk) do pk |> binary_part(0, 4) |> Base.encode16 |> String.downcase end end