Noise (Noise Protocol v0.3.0)
View SourceElixir implementation of the Noise Protocol Framework (revision 34).
A protocol is named by a string such as Noise_XX_25519_ChaChaPoly_BLAKE2s
which selects the handshake pattern and the DH, cipher and hash functions.
Supported primitives
- DH:
25519,448, andsecp256k1(BOLT-8 convention; needs the optional{:lib_secp256k1, "~> 0.8"}dependency) - Cipher:
AESGCM,ChaChaPoly - Hash:
SHA256,SHA512,BLAKE2s,BLAKE2b - Patterns: every one-way, fundamental and deferred pattern from the
spec, with
pskNmodifiers
Walkthrough: Noise_XX_25519_ChaChaPoly_BLAKE2s
XX is a three-message handshake where both parties transmit their static keys, so neither needs to know the other's key up front.
protocol = Noise.protocol("Noise_XX_25519_ChaChaPoly_BLAKE2s")
client_kp = Noise.generate_keypair(protocol)
server_kp = Noise.generate_keypair(protocol)
client = Noise.handshake(protocol, true, "prologue", s: client_kp)
server = Noise.handshake(protocol, false, "prologue", s: server_kp)
# -> e
{:ok, msg1, client} = Noise.handshake_step(client, "hello")
{:ok, "hello", server} = Noise.handshake_step(server, msg1)
# <- e, ee, s, es
{:ok, msg2, server} = Noise.handshake_step(server, "")
{:ok, "", client} = Noise.handshake_step(client, msg2)
# -> s, se (last message: both sides report :complete)
{:complete, msg3, client} = Noise.handshake_step(client, "")
{:complete, "", server} = Noise.handshake_step(server, msg3)
# Channel binding and peer identity are available from the final state
true = Noise.handshake_hash(client) == Noise.handshake_hash(server)
true = Noise.remote_static(server) == elem(client_kp, 1)
# Split into transport ciphers, already ordered as {send, receive} for each role
{client_tx, client_rx} = Noise.split(client)
{server_tx, server_rx} = Noise.split(server)
{:ok, ciphertext, client_tx} = Noise.encrypt(client_tx, "secret")
{:ok, "secret", server_rx} = Noise.decrypt(server_rx, ciphertext)handshake_step/2 writes when it is your turn to send and reads otherwise;
use write_message/2 and read_message/2 when you want that explicit.
Errors
Hostile or corrupted network input never raises. Handshake and transport
functions return {:error, reason} with one of:
:decrypt_failed- AEAD authentication failed; the state is unchanged and the message must be discarded:malformed_message- a handshake message is too short for its tokens:message_too_long- a message exceeds the 65535-byte Noise limit:invalid_public_key- the peer sent a key the DH function rejects:nonce_exhausted- the cipher state has sent/received 2^64-1 messages:wrong_turn-write_message/2called when the peer should be sending, or vice versa:handshake_complete- a handshake function called after the last message; callsplit/1
Configuration mistakes (unknown protocol name, missing static key, wrong
PSK count) raise ArgumentError from protocol/1 and handshake/4.
Summary
Types
A keypair {private_key, public_key}
A protocol name string, e.g. "Noise_IK_25519_ChaChaPoly_BLAKE2s"
Functions
Decrypts a transport message. On {:error, :decrypt_failed} the cipher state is unchanged; keep using it.
Encrypts a transport message. plain_text may be at most 65519 bytes so
the message with its tag fits the Noise limit.
Generates a fresh keypair for the protocol's DH function.
Initializes a handshake. See Noise.HandshakeState.initialize/4 for the
options (:s, :rs, :psks, …) and which patterns require them.
The handshake hash h, for channel binding (spec §11.2).
Advances the handshake: writes a message carrying data as payload when it
is this party's turn to send, otherwise reads data as the peer's message
and returns its payload.
Parses a protocol name into a Noise.Protocol. Raises ArgumentError if unsupported.
Reads the peer's handshake message and returns its payload. {:error, :wrong_turn} if it is your turn to send.
Derives a new key for the cipher state (spec §11.3). Both parties must rekey in lockstep.
The peer's static public key, once received or pre-shared.
Splits a completed handshake into {send, receive} cipher states for
this party — the initiator→responder / responder→initiator ordering of
the spec is already resolved by role. Raises unless complete.
Writes the next handshake message with payload. {:error, :wrong_turn} if the peer should send.
Types
@type cipher_state() :: Noise.CipherState.t()
@type handshake_result() :: {:ok, binary(), handshake_state()} | {:complete, binary(), handshake_state()} | {:error, Noise.HandshakeState.error()}
@type handshake_state() :: Noise.HandshakeState.t()
@type keypair() :: Noise.Crypto.DH.keypair()
A keypair {private_key, public_key}
@type protocol_name() :: String.t()
A protocol name string, e.g. "Noise_IK_25519_ChaChaPoly_BLAKE2s"
@type transport_error() :: :decrypt_failed | :nonce_exhausted | :message_too_long
Functions
@spec decrypt(cipher_state(), binary(), binary()) :: {:ok, binary(), cipher_state()} | {:error, transport_error()}
Decrypts a transport message. On {:error, :decrypt_failed} the cipher state is unchanged; keep using it.
@spec encrypt(cipher_state(), binary(), binary()) :: {:ok, binary(), cipher_state()} | {:error, transport_error()}
Encrypts a transport message. plain_text may be at most 65519 bytes so
the message with its tag fits the Noise limit.
@spec generate_keypair(Noise.Protocol.t()) :: keypair()
Generates a fresh keypair for the protocol's DH function.
@spec handshake(Noise.Protocol.t() | protocol_name(), boolean(), binary(), [ Noise.HandshakeState.option() ]) :: handshake_state()
Initializes a handshake. See Noise.HandshakeState.initialize/4 for the
options (:s, :rs, :psks, …) and which patterns require them.
@spec handshake_hash(handshake_state()) :: binary()
The handshake hash h, for channel binding (spec §11.2).
@spec handshake_step(handshake_state(), binary()) :: handshake_result()
Advances the handshake: writes a message carrying data as payload when it
is this party's turn to send, otherwise reads data as the peer's message
and returns its payload.
Returns {:complete, message_or_payload, state} on the final message.
@spec protocol(protocol_name()) :: Noise.Protocol.t()
Parses a protocol name into a Noise.Protocol. Raises ArgumentError if unsupported.
@spec read_message(handshake_state(), binary()) :: handshake_result()
Reads the peer's handshake message and returns its payload. {:error, :wrong_turn} if it is your turn to send.
@spec rekey(cipher_state()) :: cipher_state()
Derives a new key for the cipher state (spec §11.3). Both parties must rekey in lockstep.
@spec remote_static(handshake_state()) :: binary() | nil
The peer's static public key, once received or pre-shared.
@spec split(handshake_state()) :: {cipher_state(), cipher_state()}
Splits a completed handshake into {send, receive} cipher states for
this party — the initiator→responder / responder→initiator ordering of
the spec is already resolved by role. Raises unless complete.
@spec write_message(handshake_state(), binary()) :: handshake_result()
Writes the next handshake message with payload. {:error, :wrong_turn} if the peer should send.