aboutsummaryrefslogblamecommitdiff
path: root/shard/lib/keys.ex
blob: 3a97b5f9c98905a538ea6828e2039ca94211b19a (plain) (tree)
1
2
3
4
5
6

                       
                                                                                   


           










                                                                                            
                                                          


                                                               
       
       


                                     
                                                   













                                                                         



                                                                                 








                                 




                                                                                 
                                                    
                                  


                                                             






                                                                       
                                                                 











                                                                                  
                                   









                                                                                  
                               
                                  




                                  


                                                               






                                    


                                                                                      






                                    
         
                                                                                 
 
                                       
 

                                                                                           



                                    
                                            




                               
                                           
 
                                           

                              
                               



                                                         



                                  

         
                                                                                     




                                                                   



                                                                                       





                        
   
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