Packages

This is an implementation of Mark Qvist's Reticulum Network Stack, a 'cryptography-based networking stack for building local and wide-area networks with readily available hardware.'

Current section

Files

Jump to
reticulum lib rns packet announce.ex
Raw

lib/rns/packet/announce.ex

defmodule RNS.Packet.Announce do
@moduledoc """
Module for the announce packet.
"""
@type t() :: RNS.Packet.t()
require Logger
require RNS.Packet
@doc """
Handles received announce packets.
If an announce is valid, its identity will be added to the `RNS.IdentityStore` and its destination to `RNS.DestinationStore`.
"""
@spec handle(t(), RNS.Interface.t()) ::
:ok | {:error, :invalid_hash | :invalid_signature}
def handle(packet, interface_id) do
context_flag = RNS.Packet.context_flag(packet)
hops = RNS.Packet.hops(packet)
destination_hash = RNS.Packet.destination_hash(packet)
data = RNS.Packet.encrypted_data(packet)
# The ratchet size will be 0 if the context flag is unset, meaning that no ratchet is present.
ratchet_size = if context_flag, do: 32, else: 0
<<
encryption_key::binary-size(32),
signature_key::binary-size(32),
name_hash::binary-size(10),
random_hash::binary-size(10),
ratchet::binary-size(ratchet_size),
signature::binary-size(64),
app_data::binary
>> = data
identity =
RNS.Identity.new(
create_keys: false,
encryption_public: encryption_key,
signature_public: signature_key,
expiry: NaiveDateTime.add(NaiveDateTime.utc_now(), 30, :day),
store: false
)
signed_data =
<<destination_hash::binary, encryption_key::binary, signature_key::binary,
name_hash::binary, random_hash::binary, ratchet::binary, app_data::binary>>
if RNS.Identity.validate?(signed_data, signature, identity) do
identity_hash = RNS.Identity.hash(identity)
hash_material = <<name_hash::binary, identity_hash::binary>>
expected_hash = RNS.Crypto.truncated_sha256(hash_material)
if destination_hash == expected_hash do
# Add the ratchet
if ratchet != "" do
RNS.DestinationStore.put_ratchet(ratchet, destination_hash)
end
# Add the identity to the identity store, the entry will expire in 14 days.
# If the entry is already present, this will just update the expiry.
RNS.IdentityStore.put(identity)
if should_store?(destination_hash, hops) do
Logger.debug(
"Received announce for #{RNS.Destination.to_string(destination_hash)}, which is #{hops} hops away through #{interface_id}."
)
# Create and store the destination, will expire in 7 days.
destination =
RNS.Destination.new(
hash: destination_hash,
hops: hops,
expiry: NaiveDateTime.add(NaiveDateTime.utc_now(), 7, :day),
name_hash: name_hash,
identity: identity,
interface: interface_id,
app_data: app_data,
announce: packet,
store: true
)
# Run the handlers
for handler <- RNS.AnnounceHashStore.handlers(destination_hash, name_hash) do
send(handler, {:announce, destination})
end
end
else
{:error, :invalid_hash}
end
else
{:error, :invalid_signature}
end
end
@spec should_store?(RNS.Destination.hash(), RNS.Packet.hops()) :: boolean()
# No destination in store -> true
# Announced destination closer or equal than stored one -> true
# Stored destination interface dead -> true
defp should_store?(destination_hash, hops) do
case RNS.DestinationStore.fetch(destination_hash) do
{:ok, store_destination} ->
if RNS.Destination.hops(store_destination) >= hops or
not RNS.Interface.alive?(RNS.Destination.interface(store_destination)) do
true
else
false
end
{:error, :not_found} ->
true
end
end
@doc "Create a new announce from a destination(private keys need to be present)."
@spec new(RNS.Destination.t()) ::
{:ok, t()} | {:error, :not_all_identity_keys}
def new(destination) do
random_hash =
RNS.Identity.random_hash()
|> RNS.Crypto.truncate(10)
name_hash = RNS.Destination.name_hash(destination)
destination_hash = RNS.Destination.hash(destination)
app_data = RNS.Destination.app_data(destination)
identity = RNS.Destination.identity(destination)
encryption_public = RNS.Identity.encryption_public(identity)
signature_public = RNS.Identity.signature_public(identity)
encryption_secret = RNS.Identity.encryption_secret(identity)
signature_secret = RNS.Identity.signature_secret(identity)
if encryption_public != nil and signature_public != nil and encryption_secret != nil and
signature_secret != nil do
signed_data =
<<destination_hash::binary, encryption_public::binary, signature_public::binary,
name_hash::binary, random_hash::binary, app_data::binary>>
signature = RNS.Identity.sign(signed_data, signature_secret)
data = <<
encryption_public::binary-size(32),
signature_public::binary-size(32),
name_hash::binary-size(10),
random_hash::binary-size(10),
signature::binary-size(64),
app_data::binary
>>
packet = RNS.Packet.new(
data,
destination_hash,
destination_type: :plain,
packet_type: :announce,
propagation_type: :broadcast
)
{:ok, packet}
else
{:error, :not_all_identity_keys}
end
end
end