Packages
Typed distributed messaging for Gleam on the BEAM.
Retired package: Deprecated - The project needs to be redesigned around a much smaller and clearer core.
Current section
Files
Jump to
Current section
Files
src/distribute@crypto@behaviour.erl
-module(distribute@crypto@behaviour).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/crypto/behaviour.gleam").
-export([new_secure_context/4, context_node_id/1, context_stage/1, context_key_id/1, new_handshake_state/3, handshake_stage/1, handshake_remote_node/1, is_transient_error/1, is_permanent_error/1, default_options/1]).
-export_type([handshake_stage/0, secure_context/0, handshake_state/0, health_status/0, crypto_metrics/0, crypto_error/0, provider_options/0, handshake_message/0, handshake_result/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Crypto provider behaviour contract for secure communications.\n"
"\n"
" This module defines the contract that crypto providers must implement\n"
" to provide encryption, key exchange, and secure context management\n"
" in the distribute library.\n"
"\n"
" ## Design Philosophy\n"
"\n"
" This behaviour follows distribute's patterns:\n"
" - Pure function signatures (like `transport/adapter`)\n"
" - Actor-based implementations for state management\n"
" - Integration with `handshake` for key exchange\n"
" - Opaque handles for secure context (no key leakage)\n"
"\n"
" ## Implementation Approaches\n"
"\n"
" Providers can be implemented as:\n"
" 1. **No-op provider**: Identity transformation for development (insecure!)\n"
" 2. **AEAD provider**: AES-GCM or ChaCha20-Poly1305\n"
" 3. **ECDH provider**: Elliptic curve key exchange + symmetric encryption\n"
"\n"
" ## State Machine\n"
"\n"
" Key exchange follows a state machine:\n"
" ```\n"
" Plain -> KeyExchangeInProgress -> SecureEstablished\n"
" |\n"
" v\n"
" Rekeying\n"
" |\n"
" v\n"
" SecureEstablished\n"
" ```\n"
"\n"
" Failed states can occur at any transition.\n"
"\n"
" ## Security Considerations\n"
"\n"
" - Never log key material\n"
" - Use opaque handles for keys and contexts\n"
" - Zero sensitive memory on shutdown when possible\n"
" - Mark development providers as insecure\n"
"\n"
" ## Example Implementation\n"
"\n"
" See `distribute/crypto/noop_adapter` for a reference implementation.\n"
).
-type handshake_stage() :: plain |
key_exchange_in_progress |
secure_established |
rekeying |
{failed, binary()}.
-opaque secure_context() :: {secure_context,
binary(),
handshake_stage(),
integer(),
binary()}.
-opaque handshake_state() :: {handshake_state,
binary(),
binary(),
handshake_stage(),
gleam@dict:dict(binary(), binary())}.
-type health_status() :: up | {degraded, binary()} | {down, binary()}.
-type crypto_metrics() :: {crypto_metrics,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer()}.
-type crypto_error() :: {init_failed, binary()} |
{shutdown_failed, binary()} |
{transient_network, binary()} |
invalid_signature |
key_mismatch |
{decryption_failed, binary()} |
{encryption_failed, binary()} |
{no_secure_context, binary()} |
{handshake_failed, binary()} |
{rekey_failed, binary()} |
{provider_failure, binary()} |
{timeout, integer()}.
-type provider_options() :: {provider_options,
binary(),
boolean(),
integer(),
integer(),
gleam@dict:dict(binary(), binary())}.
-type handshake_message() :: {handshake_message,
binary(),
bitstring(),
gleam@option:option(gleam@dict:dict(binary(), binary()))}.
-type handshake_result() :: {continue,
handshake_state(),
gleam@option:option(handshake_message())} |
{established, secure_context()} |
{handshake_error, crypto_error()}.
-file("src/distribute/crypto/behaviour.gleam", 106).
?DOC(
" Create a new secure context (for provider implementations).\n"
"\n"
" Constructs a `SecureContext` for storing encrypted session state.\n"
" This is a simpler version that doesn't include key material - use\n"
" `types.new_secure_context` for full context with key material.\n"
"\n"
" ## Arguments\n"
"\n"
" - `node_id` - Remote node this context is for\n"
" - `stage` - Current handshake stage\n"
" - `created_at_ms` - Creation timestamp\n"
" - `key_id` - Unique key identifier (safe to log)\n"
"\n"
" ## Note\n"
"\n"
" This is primarily for the behaviour module's internal use.\n"
" Most providers should use `types.new_secure_context` instead.\n"
).
-spec new_secure_context(binary(), handshake_stage(), integer(), binary()) -> secure_context().
new_secure_context(Node_id, Stage, Created_at_ms, Key_id) ->
{secure_context, Node_id, Stage, Created_at_ms, Key_id}.
-file("src/distribute/crypto/behaviour.gleam", 124).
?DOC(
" Get the node ID from a secure context.\n"
"\n"
" Returns the identifier of the remote node this secure context\n"
" was established with during handshake.\n"
).
-spec context_node_id(secure_context()) -> binary().
context_node_id(Ctx) ->
erlang:element(2, Ctx).
-file("src/distribute/crypto/behaviour.gleam", 132).
?DOC(
" Get the handshake stage from a secure context.\n"
"\n"
" Returns the current state of the security handshake. Should be\n"
" `SecureEstablished` before using the context for encryption.\n"
).
-spec context_stage(secure_context()) -> handshake_stage().
context_stage(Ctx) ->
erlang:element(3, Ctx).
-file("src/distribute/crypto/behaviour.gleam", 140).
?DOC(
" Get the key ID from a secure context.\n"
"\n"
" Returns a unique identifier for the current encryption key.\n"
" Safe to log for debugging; changes after each rekey operation.\n"
).
-spec context_key_id(secure_context()) -> binary().
context_key_id(Ctx) ->
erlang:element(5, Ctx).
-file("src/distribute/crypto/behaviour.gleam", 167).
?DOC(
" Create a new handshake state (for provider implementations).\n"
"\n"
" Constructs intermediate state for tracking an in-progress handshake.\n"
" Used internally by providers to maintain state between handshake steps.\n"
"\n"
" ## Arguments\n"
"\n"
" - `local_node` - Local node identifier\n"
" - `remote_node` - Remote node identifier \n"
" - `stage` - Current handshake stage\n"
).
-spec new_handshake_state(binary(), binary(), handshake_stage()) -> handshake_state().
new_handshake_state(Local_node, Remote_node, Stage) ->
{handshake_state, Local_node, Remote_node, Stage, maps:new()}.
-file("src/distribute/crypto/behaviour.gleam", 184).
?DOC(
" Get the stage from handshake state.\n"
"\n"
" Returns the current stage of the handshake process. Use to determine\n"
" if the handshake needs more message exchanges or has completed.\n"
).
-spec handshake_stage(handshake_state()) -> handshake_stage().
handshake_stage(State) ->
erlang:element(4, State).
-file("src/distribute/crypto/behaviour.gleam", 192).
?DOC(
" Get the remote node from handshake state.\n"
"\n"
" Returns the identifier of the remote node this handshake is\n"
" being conducted with.\n"
).
-spec handshake_remote_node(handshake_state()) -> binary().
handshake_remote_node(State) ->
erlang:element(3, State).
-file("src/distribute/crypto/behaviour.gleam", 271).
?DOC(
" Check if an error is transient and should be retried.\n"
"\n"
" Returns `True` for errors that are temporary and may succeed\n"
" on retry (e.g., network issues, timeouts).\n"
).
-spec is_transient_error(crypto_error()) -> boolean().
is_transient_error(Error) ->
case Error of
{transient_network, _} ->
true;
{timeout, _} ->
true;
_ ->
false
end.
-file("src/distribute/crypto/behaviour.gleam", 283).
?DOC(
" Check if an error is permanent and should not be retried.\n"
"\n"
" Returns `True` for errors that indicate a fundamental problem\n"
" that won't be resolved by retrying (e.g., authentication failure).\n"
).
-spec is_permanent_error(crypto_error()) -> boolean().
is_permanent_error(Error) ->
case Error of
invalid_signature ->
true;
key_mismatch ->
true;
{decryption_failed, _} ->
true;
{init_failed, _} ->
true;
_ ->
false
end.
-file("src/distribute/crypto/behaviour.gleam", 314).
?DOC(" Default provider options.\n").
-spec default_options(binary()) -> provider_options().
default_options(Name) ->
{provider_options, Name, false, 0, 30000, maps:new()}.