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 = <> if RNS.Identity.validate?(signed_data, signature, identity) do identity_hash = RNS.Identity.hash(identity) hash_material = <> 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 = <> 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