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}] ->
{: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
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}] ->
{: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
def pk_display(pk) do
pk
|> binary_part(0, 4)
|> Base.encode16
|> String.downcase
end
end