Packages

Library implementing Noise protocol

Current section

Files

Jump to
noise_protocol lib noise.ex
Raw

lib/noise.ex

defmodule Noise do
@moduledoc """
Noise Protocol Framework.
This module provides the public API for interacting with the Noise protocol implementation.
It allows you to initialize handshakes, process handshake messages, and perform encryption/decryption
using the established sessions.
## Example
# Initiator
protocol = Noise.protocol("Noise_IK_25519_ChaChaPoly_BLAKE2s")
kp_i = Noise.generate_keypair(protocol)
state_i = Noise.handshake(protocol, true, <<>>, s: kp_i, rs: kp_r_pub)
# Responder
state_r = Noise.handshake(protocol, false, <<>>, s: kp_r)
# Handshake step 1 (Initiator writes)
{:ok, msg1, state_i} = Noise.handshake_step(state_i, "payload1")
# Handshake step 1 (Responder reads)
{:ok, "payload1", state_r} = Noise.handshake_step(state_r, msg1)
# ... continue until split ...
"""
alias Noise.Protocol
alias Noise.HandshakeState
alias Noise.Handshake
alias Noise.CipherState
@type protocol_name :: String.t()
@type keypair :: {binary(), binary()}
@type key :: binary()
@type handshake_state :: HandshakeState.t()
@type cipher_state :: CipherState.t()
@doc """
Parses a protocol name string into a `Noise.Protocol` struct.
"""
@spec protocol(protocol_name()) :: Protocol.t()
def protocol(name), do: Protocol.from_name(name)
@doc """
Generates a keypair for the given protocol.
"""
@spec generate_keypair(Protocol.t()) :: keypair()
def generate_keypair(%Protocol{} = protocol) do
Protocol.generate_keypair(protocol)
end
@doc """
Initializes a handshake state.
## Arguments
* `protocol` - A `Noise.Protocol` struct or protocol name string.
* `initiator` - Boolean, `true` if this party is the initiator, `false` otherwise.
* `prologue` - (Optional) Prologue data, defaults to empty binary.
* `opts` - Keyword list of options:
* `:s` - Local static keypair `{priv, pub}`.
* `:rs` - Remote static public key.
* `:e` - Local ephemeral keypair (usually generated automatically, provided for testing/determinism).
* `:re` - Remote ephemeral public key.
* `:psks` - List of pre-shared keys.
"""
@spec handshake(Protocol.t() | protocol_name(), boolean(), binary(), keyword()) :: handshake_state()
def handshake(protocol, initiator, prologue \\ <<>>, opts \\ []) do
s = Keyword.get(opts, :s)
rs = Keyword.get(opts, :rs)
e = Keyword.get(opts, :e)
re = Keyword.get(opts, :re)
psks = Keyword.get(opts, :psks, [])
HandshakeState.initialize(protocol, initiator, prologue, s, rs, e, re, psks)
end
@doc """
Advances the handshake state machine.
If the current turn is to **write** a message, `data` should be the plaintext payload to send.
If the current turn is to **read** a message, `data` should be the received ciphertext.
Returns:
* `{:ok, message, new_state}` - The handshake continues. `message` is the ciphertext to send (if writing) or the decrypted payload (if reading).
* `{:complete, message, {cs1, cs2}}` - The handshake is completed with this step. `message` is the final output, and `{cs1, cs2}` are the split CipherStates.
* `{:split, {cs1, cs2}}` - The handshake was already ready to split (called on a state with no remaining patterns).
"""
@spec handshake_step(handshake_state(), binary()) ::
{:ok, binary(), handshake_state()}
| {:complete, binary(), {cipher_state(), cipher_state()}}
| {:split, {cipher_state(), cipher_state()}}
def handshake_step(state, data \\ <<>>) do
case Handshake.next_step(state, data) do
{{%CipherState{}, %CipherState{}} = split_states, _state} ->
{:split, split_states}
{msg, %HandshakeState{message_patterns: []} = new_state} when is_binary(msg) ->
{{c1, c2}, _} = HandshakeState.finalize(new_state)
{:complete, msg, {c1, c2}}
{msg, new_state} when is_binary(msg) ->
{:ok, msg, new_state}
end
end
@doc """
Encrypts data using the given CipherState.
Returns `{ciphertext, new_cipher_state}`.
Updates the nonce in the CipherState.
"""
@spec encrypt(cipher_state(), binary(), binary()) :: {binary(), cipher_state()}
def encrypt(cipher_state, plain_text, ad \\ <<>>) do
CipherState.encrypt_with_ad(cipher_state, ad, plain_text)
end
@doc """
Decrypts data using the given CipherState.
Returns `{plaintext, new_cipher_state}`.
Updates the nonce in the CipherState.
"""
@spec decrypt(cipher_state(), binary(), binary()) :: {binary(), cipher_state()}
def decrypt(cipher_state, cipher_text, ad \\ <<>>) do
CipherState.decrypt_with_ad(cipher_state, ad, cipher_text)
end
@doc """
Rekey the CipherState.
"""
@spec rekey(cipher_state()) :: cipher_state()
def rekey(cipher_state) do
CipherState.rekey(cipher_state)
end
end