Packages
baobab
0.16.2
0.35.1
0.35.0
0.34.0
0.31.0
0.30.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.0
Bamboo append-only logs
Current section
Files
Jump to
Current section
Files
lib/baobab/entry/validator.ex
defmodule Baobab.Entry.Validator do
alias Baobab.Persistence
@moduledoc """
Validation of `Baobab.Entry` structs
"""
@doc """
Validate a `Baobab.Entry` struct relative to the provided clump_id
Includes validation of its available certificate pool
"""
@spec validate(String.t(), %Baobab.Entry{}) :: %Baobab.Entry{} | {:error, String.t()}
def validate(clump_id, %Baobab.Entry{seqnum: seq, author: author, log_id: log_id} = entry) do
case validate_entry(clump_id, entry) do
:ok ->
case verify_chain(
Baobab.certificate_pool(author, seq, log_id, clump_id),
{clump_id, author, log_id},
:ok
) do
:ok -> entry
error -> error
end
error ->
error
end
end
def validate(_, _), do: {:error, "Input is not a Baobab.Entry"}
defp verify_chain([], _log, answer), do: answer
defp verify_chain(_links, _log, answer) when is_tuple(answer), do: answer
defp verify_chain([seq | rest], {clump_id, author, log_id} = which, _answer) do
new_answer =
case Persistence.retrieve(author, seq, {:entry, log_id, false, clump_id}) do
:error ->
{:error, "Could not retrieve certificate chain seqnum: " <> Integer.to_string(seq)}
link ->
validate_link(clump_id, link)
end
verify_chain(rest, which, new_answer)
end
defp validate_link(clump_id, entry) do
with :ok <- validate_sig(entry),
:ok <- validate_backlink(clump_id, entry),
:ok <- validate_lipmaalink(clump_id, entry) do
:ok
else
error -> error
end
end
@doc """
Validate a `Baobab.Entry` without full certificate pool verification.
Confirms:
- Signature
- Payload hash
- Backlink
- Lipmaalink
Relative to the provided `clump_id`
"""
@spec validate_entry(String.t(), %Baobab.Entry{}) :: :ok | {:error, String.t()}
def validate_entry(clump_id, entry) do
with :ok <- validate_sig(entry),
:ok <- validate_payload_hash(entry),
:ok <- validate_backlink(clump_id, entry),
:ok <- validate_lipmaalink(clump_id, entry) do
:ok
else
error -> error
end
end
@doc """
Validate the `sig` field of a `Baobab.Entry`
"""
@spec validate_sig(%Baobab.Entry{}) :: :ok | {:error, String.t()}
def validate_sig(%Baobab.Entry{
tag: tag,
sig: sig,
author: author,
seqnum: seq,
size: size,
payload_hash: payload_hash,
log_id: log_id,
lipmaalink: lipmaa,
backlink: back
}) do
head = tag <> author <> Varu64.encode(log_id) <> Varu64.encode(seq)
ll =
case lipmaa do
nil -> <<>>
val -> val
end
bl =
case back do
nil -> <<>>
val -> val
end
tail = Varu64.encode(size) <> payload_hash
case :enacl.sign_verify_detached(sig, head <> ll <> bl <> tail, author) do
true -> :ok
false -> {:error, "Invalid signature"}
end
end
@doc """
Validate the `payload_hash` field of a `Baobab.Entry`
"""
@spec validate_payload_hash(%Baobab.Entry{}) :: :ok | {:error, String.t()}
def validate_payload_hash(%Baobab.Entry{payload: payload, payload_hash: hash}) do
case YAMFhash.verify(hash, payload) do
<<>> -> :ok
_ -> {:error, "Invalid payload hash"}
end
end
@doc """
Validate the `lipmaalink` field of a `Baobab.Entry` relatve to the provided clump_id
"""
@spec validate_lipmaalink(String.t(), %Baobab.Entry{}) :: :ok | {:error, String.t()}
def validate_lipmaalink(_clump_id, %Baobab.Entry{seqnum: 1, lipmaalink: nil}), do: :ok
def validate_lipmaalink(clump_id, %Baobab.Entry{
author: author,
log_id: log_id,
seqnum: seq,
lipmaalink: ll
}) do
case {seq - 1, Lipmaa.linkseq(seq), ll} do
{n, n, nil} ->
:ok
{n, n, _} ->
{:error, "Invalid lipmaa link when matches backlink"}
{_, n, ll} ->
case Persistence.content(:entry, :contents, {author, log_id, n}, clump_id) do
:error ->
{:error, "Missing lipmaalink entry for verificaton"}
fll ->
case YAMFhash.verify(ll, fll) do
<<>> -> :ok
_ -> {:error, "Invalid lipmaalink hash"}
end
end
end
end
@doc """
Validate the `backlink` field of a `Baobab.Entry` relative to the provided clump
"""
@spec validate_backlink(String.t(), %Baobab.Entry{}) :: :ok | {:error, String.t()}
def validate_backlink(_, %Baobab.Entry{seqnum: 1, backlink: nil}), do: :ok
def validate_backlink(_, %Baobab.Entry{backlink: nil}),
do: {:error, "Missing required backlink"}
def validate_backlink(clump_id, %Baobab.Entry{
author: author,
log_id: log_id,
seqnum: seq,
backlink: bl
}) do
case Persistence.content(:entry, :contents, {author, log_id, seq - 1}, clump_id) do
# We don't have it so we cannot check it. We'll say it's OK
# This is required for partial replication to be meaningful.
# I am sure I will come to regret this post-haste
:error ->
:ok
back_entry ->
case YAMFhash.verify(bl, back_entry) do
<<>> -> :ok
_ -> {:error, "Invalid backlink hash"}
end
end
end
end