core/crypto/noise
noise
Types
7Cipher_State
Cipher_State :: struct {
ctx: aead.Context,
n: u64,
n_exhausted: bool,
is_invalid: bool,
}SourceCipher_States
Cipher_States :: struct {
c1_i_to_r: Cipher_State,
c2_r_to_i: Cipher_State,
initiator: bool,
}SourceCipher_States are the keyed AEAD instances and associated state, derived from a successful handshake.
Handshake_Pattern
Handshake_Pattern :: enum int {
Invalid = 0,
// One way patterns
N = 1,
K = 2,
X = 3,
// Fundamental patterns
XX = 4,
NK = 5,
NN = 6,
KN = 7,
KK = 8,
NX = 9,
KX = 10,
XN = 11,
IN = 12,
XK = 13,
IK = 14,
IX = 15,
// Deferred patterns
NK1 = 16,
NX1 = 17,
X1N = 18,
X1K = 19,
XK1 = 20,
X1K1 = 21,
X1X = 22,
XX1 = 23,
X1X1 = 24,
K1N = 25,
K1K = 26,
KK1 = 27,
K1K1 = 28,
K1X = 29,
KX1 = 30,
K1X1 = 31,
I1N = 32,
I1K = 33,
IK1 = 34,
I1K1 = 35,
I1X = 36,
IX1 = 37,
I1X1 = 38,
// Recommended PSK patterns
Npsk0 = 39,
Kpsk0 = 40,
Xpsk1 = 41,
NNpsk0 = 42,
NNpsk2 = 43,
NKpsk0 = 44,
NKpsk2 = 45,
NXpsk2 = 46,
XNpsk3 = 47,
XKpsk3 = 48,
XXpsk3 = 49,
KNpsk0 = 50,
KNpsk2 = 51,
KKpsk0 = 52,
KKpsk2 = 53,
KXpsk2 = 54,
INpsk1 = 55,
INpsk2 = 56,
IKpsk1 = 57,
IKpsk2 = 58,
IXpsk2 = 59,
}SourceHandshake_Pattern is the list of currently supported Noise Handshake Patterns.
Handshake_State
Handshake_State :: struct {
s: ecdh.Private_Key,
e: ecdh.Private_Key,
rs: ecdh.Public_Key,
re: ecdh.Public_Key,
psk: [32]u8,
symmetric_state: Symmetric_State,
message_pattern: ^Message_Pattern,
current_message: int,
status: Status,
initiator: bool,
pre_set_e: bool,
}SourceHandshake_State is the per-handshake state.
Protocol
Protocol :: struct {
handshake_pattern: Handshake_Pattern,
dh: ecdh.Curve,
cipher: aead.Algorithm,
hash: hash.Algorithm,
}SourceStatus
Status :: enum int {
Ok = 0,
// States
Handshake_Pending = 1,
Handshake_Complete = 2,
Handshake_Split = 3,
Handshake_Failed = 4,
// Errors
Invalid_Protocol_String = 5,
Invalid_Pre_Shared_Key = 6,
Invalid_DH_Key = 7,
No_Self_Identity = 8,
No_Peer_Identity = 9,
Unexpected_Peer_Identity = 10,
Unexpected_Pre_Shared_Key = 11,
DH_Failure = 12,
Invalid_Handshake_Message = 13,
Decryption_Failure = 14,
IV_Exhausted = 15,
Invalid_Cipher_State = 16,
Invalid_Destination_Buffer = 17,
Invalid_Payload_Message = 18,
Max_Packet_Size = 19,
Out_Of_Memory = 20,
}SourceStatus is the status of Noise protocol operation.
Symmetric_State
Symmetric_State :: struct {
protocol: Protocol,
cipher_state: Cipher_State,
_ck: [64]u8,
_h: [64]u8,
}SourceConstants
8AEAD_KEY_SIZE
AEAD_KEY_SIZE :: 32SourceMAX_DH_SIZE
MAX_DH_SIZE :: 56SourceMAX_HASH_SIZE
MAX_HASH_SIZE :: 64SourceMAX_PACKET_SIZE
MAX_PACKET_SIZE :: 65535SourceMAX_PACKET_SIZE is the maximum Noise message size, including TAG_SIZE if relevant (seal_message, open_message).
MAX_STEP_MSG_SIZE
MAX_STEP_MSG_SIZE :: (MAX_DH_SIZE*2)+TAG_SIZE+TAG_SIZESourceMAX_STEP_MSG_SIZE is the maximum per-handshake step message size, excluding the optional payload.
e is DH_LEN, s is either DH_LEN or DH_LEN + TAG_SIZE, and there is a maximum of one per each message, and a possible mandatory tag.
MIN_DH_SIZE
MIN_DH_SIZE :: 32SourcePSK_SIZE
PSK_SIZE :: 32SourcePSK_SIZE is the size of an optional handshake pre-shared symmetric key.
TAG_SIZE
TAG_SIZE :: 16SourceTAG_SIZE is the size of the AEAD authentication tag.
Procedures
49_decrypt
_decrypt :: proc(ctx: ^aead.Context, n: u64, ad: []u8, ciphertext: []u8, dst: []u8) -> (Status)SourceDecrypts ciphertext using a cipher key k of 32 bytes, an 8-byte unsigned integer nonce n, and associated data ad. Returns the plaintext, unless authentication fails, in which case an error is signaled to the caller.
_dh
_dh :: proc(our_private_key: ^ecdh.Private_Key, their_public_key: ^ecdh.Public_Key, dst: []u8) -> (Status)SourcePerforms a Diffie-Hellman calculation between the private key in key_pair and the public_key and returns an output sequence of bytes of length DHLEN. For security, the Gap-DH problem based on this function must be unsolvable by any practical cryptanalytic adversary [2].
The public_key either encodes some value which is a generator in a large prime-order group (which value may have multiple equivalent encodings), or is an invalid value. Implementations must handle invalid public keys either by returning some output which is purely a function of the public key and does not depend on the private key, or by signaling an error to the caller.
The DH function may define more specific rules for handling invalid values.
_encrypt
_encrypt :: proc(ctx: ^aead.Context, n: u64, ad: []u8, plaintext: []u8, dst: []u8)SourceEncrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique for the key k. Returns the ciphertext. Encryption must be done with an "AEAD" encryption mode with the associated data(AD) (using the terminology from [1]) and returns a ciphertext that is the same size as the plaintext plus 16 bytes for authentication data. The entire ciphertext must be indistinguishable from random if the key is secret (note that this is an additional requirement that isn't necessarily met by all AEAD schemes).
_hash
_hash :: proc(dst: []u8, protocol: ^Protocol, data)SourceHashes some arbitrary-length data with a collision-resistant cryptographic hash function and returns an output of HASHLEN bytes.
_hkdf
_hkdf :: proc(dst: []u8, chaining_key: []u8, input_key_material: []u8, protocol: ^Protocol) -> ([]u8, []u8, []u8)SourceTakes a chaining_key byte sequence of length HASHLEN, and an input_key_material byte sequence with length either zero bytes, 32 bytes, or DHLEN bytes. Returns a pair or triple of byte sequences each of length HASHLEN, depending on whether num_outputs is two or three:
- Sets temp_key = HMAC-HASH(chaining_key, input_key_material).
- Sets output1 = HMAC-HASH(temp_key, byte(0x01)).
- Sets output2 = HMAC-HASH(temp_key, output1 || byte(0x02)).
- If num_outputs == 2 then returns the pair (output1, output2).
- Sets output3 = HMAC-HASH(temp_key, output2 || byte(0x03)).
- Returns the triple (output1, output2, output3).
Note that temp_key, output1, output2, and output3 are all HASHLEN bytes in length. Also note that the HKDF() function is simply HKDF from [4] with the chaining_key as HKDF salt, and zero-length HKDF info.
cipherstate_decrypt_with_ad
cipherstate_decrypt_with_ad :: proc(self: ^Cipher_State, ad: []u8, ciphertext: []u8, dst: []u8) -> ([]u8, Status)SourceIf k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise returns ciphertext. If an authentication failure occurs in DECRYPT() then n is not incremented and an error is signaled to the caller.
cipherstate_encrypt_with_ad
cipherstate_encrypt_with_ad :: proc(self: ^Cipher_State, ad: []u8, plaintext: []u8, dst: []u8) -> ([]u8, Status)SourceIf k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise returns plaintext.
cipherstate_has_key
cipherstate_has_key :: proc(self: ^Cipher_State) -> (bool)SourceReturns true if k is non-empty, false otherwise.
cipherstate_initialize_key
cipherstate_initialize_key :: proc(self: ^Cipher_State, key: []u8, protocol: ^Protocol)SourceSets k = key. Sets n = 0.
cipherstate_rekey
cipherstate_rekey :: proc(self: ^Cipher_State)SourceSets k = REKEY(k).
cipherstate_reset
cipherstate_reset :: proc(self: ^Cipher_State)Sourcecipherstates_n
cipherstates_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> (Status, u64)Sourcecipherstates_n returns the interal counter used to generate the AEAD IV. This can be used to deal with out-of-order transport messages. See 11.4 of the specification.
WARNING: Reusing n across different aad/messages with the same Cipher_States will result in catastrophic loss of security.
cipherstates_rekey
cipherstates_rekey :: proc(self: ^Cipher_States, seal_key: bool) -> (Status)Sourcecipherstates_rekey updates the selected AEAD key, using a one way function. See 11.3 of the specification for examples of usage.
Note: If one side updates the seal_key, the other side must update the non-seal_key and vice versa.
cipherstates_reset
cipherstates_reset :: proc(self: ^Cipher_States)Sourcecipherstates_reset sanitizes the Cipher_States.
cipherstates_set_n
cipherstates_set_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> (Status)Sourcecipherstates_set_n sets the interal counter used to generate the AEAD IV to an explicit value. This can be used to deal with out-of-order transport messages. See 11.4 of the specification.
WARNING: Reusing n across different aad/messages with the same Cipher_States will result in catastrophic loss of security.
dh_len
dh_len :: proc(protocol: ^Protocol) -> (int)Sourcegenerate_keypair
generate_keypair :: proc(protocol: ^Protocol, private_key: ^ecdh.Private_Key)SourceGenerates a new Diffie-Hellman key pair. A DH key pair consists of public_key and private_key elements. public_key represents an encoding of a DH public key into a byte sequence of length DHLEN. The public_key encoding details are specific to each set of DH functions.
handshake_hash
handshake_hash :: proc(self: ^Handshake_State) -> ([]u8, Status)Sourcehandshake_hash returns the handshake transcript hash of a completed handshake, for the purposes of channel binding. See 11.2 of the specification for details on usage.
This returns a slice to an internal buffer that will get wiped by handshake_reset. If the hash is needed after a call to handshake_reset, the slice must be copied.
handshake_init
handshake_init :: proc(
self: ^Handshake_State,
initiator: bool,
prologue: []u8,
s: ^ecdh.Private_Key,
rs: ^ecdh.Public_Key,
protocol_name: string,
psk: []u8,
_e: ^ecdh.Private_Key,
) -> (Status)Sourcehandshake_init initializes a Handshake_State with the provided parameters. The relevant values are copied into the Handshake_State instance, and can be discarded/sanitized right after handshake_init returns (eg: psk).
Note: While this implementation supports setting e, this is primarily intended for testing, or cases where the runtime cryptographic entropy source is unavailable. Use of this functionality is STRONGLY discouraged.
handshake_initiator_step
handshake_initiator_step :: proc(self: ^Handshake_State, input_message: []u8, payload: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, []u8, Status)Sourcehandshake_initiator_step takes an input_message received from the responder if any and an optional payload to be sent to the responder, and performs one step of the Noise handshake process, returning the message to be sent to the responder if any, the payload received from the responder if any, and the status of the handshake.
The output message MUST be sent to the responder even if the status code returned is .Handshake_Complete.
If the dst parameter is provided, the message and payload will be written to dst, otherwise new buffers will be allocated.
handshake_peer_identity
handshake_peer_identity :: proc(self: ^Handshake_State) -> (^ecdh.Public_Key, Status)Sourcehandshake_peer_identity returns the peer's static DH key used by a completed handshake.
This returns a pointer to the Handshake_State's copy of the peer's public key, that will get wiped by handshake_reset. If the key is needed after a call to handshake_reset, it must be copied.
handshake_read_message
handshake_read_message :: proc(self: ^Handshake_State, message: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)Sourcehandshake_read_message calls the Noise HandshakeState's ReadMessage function directly. In most cases you are better off using handshake_initiator_step or handshake_responder_step.
If the dst parameter is provided, the message and payload will be written to dst, otherwise new buffers will be allocated.
handshake_reset
handshake_reset :: proc(self: ^Handshake_State)Sourcehandshake_reset sanitizes the Handshake_State. It is both safe and recommended to call this as soon as practical (after any calls to handshake_peer_identity, handshake_hash, and handshake_split are complete).
handshake_responder_step
handshake_responder_step :: proc(self: ^Handshake_State, input_message: []u8, payload: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, []u8, Status)Sourcehandshake_responder_step takes a input_message received from the initiator, and and an optional payload to be sent to the initiator, and performs one step of the Noise handshake process, returning the message to be sent to the initiator if any, the payload received from the initiator if any, and the status of the handshake.
The output message MUST be sent to the initiator even if the status code returned is .Handshake_Complete.
If the dst parameter is provided, the message and payload will be written to dst, otherwise new buffers will be allocated.
handshake_split
handshake_split :: proc(self: ^Handshake_State, cipher_states: ^Cipher_States) -> (Status)Sourcehandshake_split initializes a Cipher_States instance from a completed handshake. This can be called once and only once per Handshake_State instance.
handshake_write_message
handshake_write_message :: proc(self: ^Handshake_State, payload: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)Sourcehandshake_write_message calls the Noise HandshakeState's WriteMessage function directly. In most cases you are better off using handshake_initiator_step or handshake_responder_step.
If the dst parameter is provided, the message and payload will be written to dst, otherwise new buffers will be allocated.
handshakestate_initialize
handshakestate_initialize :: proc(
handshake_state: ^Handshake_State,
initiator: bool,
prologue: []u8,
s: ^ecdh.Private_Key,
e: ^ecdh.Private_Key,
rs: ^ecdh.Public_Key,
re: ^ecdh.Public_Key,
protocol_name: string,
psk: []u8,
) -> (Status)SourceTakes a valid handshake_pattern (see Section 7) and an initiator boolean specifying this party's role as either initiator or responder. Takes a prologue byte sequence which may be zero-length, or which may contain context information that both parties want to confirm is identical (see Section 6).
Takes a set of DH key pairs (s, e) and public keys (rs, re) for initializing local variables, any of which may be empty. Public keys are only passed in if the handshake_pattern uses pre-messages (see Section 7). The ephemeral values (e, re) are typically left empty, since they are created and exchanged during the handshake; but there are exceptions (see Section 10).
Performs the following steps:
- Derives a protocol_name byte sequence by combining the names for
the handshake pattern and crypto functions, as specified in Section 8.
- Calls InitializeSymmetric(protocol_name).
- Calls MixHash(prologue).
- Sets the initiator, s, e, rs, and re variables to the corresponding
arguments.
- Calls MixHash() once for each public key listed in the pre-messages
from handshake_pattern, with the specified public key as input
(see Section 7 for an explanation of pre-messages).
- If both initiator and responder have pre-messages, the initiator's
public keys are hashed first.
- If multiple public keys are listed in either party's pre-message,
the public keys are hashed in the order that they are listed.
- Sets message_pattern to the message patterns from handshake_pattern.handshakestate_read_message
handshakestate_read_message :: proc(self: ^Handshake_State, message: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)SourceTakes a byte sequence containing a Noise handshake message, and a payload_buffer to write the message's plaintext payload into. Performs the following steps, aborting if any DecryptAndHash() call returns an error:
- Fetches and deletes the next message pattern from message_pattern,
then sequentially processes each token from the message pattern:
- For "e": Sets re (which must be empty) to the next DHLEN bytes
from the message. Calls MixHash(re.public_key).
- For "s": Sets temp to the next DHLEN + 16 bytes of the message
if HasKey() == True, or to the next DHLEN bytes otherwise. Sets rs (which must be empty) to DecryptAndHash(temp).
- For "ee": Calls MixKey(DH(e, re)).
- For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
if responder.
- For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
if responder. -For "ss": Calls MixKey(DH(s, rs)).
- Calls DecryptAndHash() on the remaining bytes of the message and stores
the output into payload_buffer. – (SKIPPED) If there are no more message patterns returns two new CipherState objects by calling Split().
Calling Split() is left to a separate function, although it is technically part of the specification.
handshakestate_reset
handshakestate_reset :: proc(self: ^Handshake_State)Sourcehandshakestate_write_message
handshakestate_write_message :: proc(self: ^Handshake_State, payload: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)SourceTakes a payload byte sequence which may be zero-length, and a message_buffer to write the output into. Performs the following steps, aborting if any EncryptAndHash() call returns an error:
- Fetches and deletes the next message pattern from message_pattern,
then sequentially processes each token from the message pattern:
- For "e": Sets e (which must be empty) to GENERATE_KEYPAIR().
Appends e.public_key to the buffer. Calls MixHash(e.public_key).
- For "s": Appends EncryptAndHash(s.public_key) to the buffer.
- For "ee": Calls MixKey(DH(e, re)).
- For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
if responder.
- For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
if responder.
- For "ss": Calls MixKey(DH(s, rs)).
- Appends EncryptAndHash(payload) to the buffer.
– (SKIPPED) If there are no more message patterns returns two new
CipherState objects by calling Split().
Calling Split() is left to a separate function, although it is technically
part of the specification.hash_len
hash_len :: proc(protocol: ^Protocol) -> (int)Sourceopen_message
open_message :: proc(self: ^Cipher_States, aad: []u8, ciphertext: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)Sourceopen_message authenticates the aad and ciphertext, decrypts the ciphertext and returns the resulting plaintext. The plaintext will ALWAYS be len(ciphertext) - TAG_SIZE bytes in length.
If the dst parameter is provided, the plaintext will be written to dst, otherwise a new buffer will be allocated.
pattern_is_one_way
pattern_is_one_way :: proc(pattern: Handshake_Pattern) -> (bool)Sourcepattern_is_psk
pattern_is_psk :: proc(pattern: Handshake_Pattern) -> (bool)Sourcepattern_num_messages
pattern_num_messages :: proc(pattern: Handshake_Pattern) -> (int)Sourcepattern_requires_initiator_s
pattern_requires_initiator_s :: proc(pattern: Handshake_Pattern) -> (pre: bool, hs: bool)Sourcepattern_requires_responder_s
pattern_requires_responder_s :: proc(pattern: Handshake_Pattern) -> (pre: bool, hs: bool)Sourceprotocol_from_string
protocol_from_string :: proc(self: ^Protocol, protocol_name: string) -> (Status)Sourceseal_message
seal_message :: proc(self: ^Cipher_States, aad: []u8, plaintext: []u8, dst: []u8, allocator: mem.Allocator = context.allocator) -> ([]u8, Status)Sourceseal_message encrypts the provided data, authenticates the aad and ciphertext, and returns the resulting ciphertext. The ciphertext will ALWAYS be len(plaintext) + TAG_SIZE bytes in length.
If the dst parameter is provided, the ciphertext will be written to dst, otherwise a new buffer will be allocated.
split_protocol_string
split_protocol_string :: proc(protocol_name: string) -> (ecdh.Curve, aead.Algorithm, hash.Algorithm, Status, Handshake_Pattern)Sourcesplit_protocol_string splits a protocol string into individual components.
symmetricstate_decrypt_and_hash
symmetricstate_decrypt_and_hash :: proc(self: ^Symmetric_State, ciphertext: []u8, dst: []u8) -> ([]u8, Status)SourceSets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext), and returns plaintext.
Note that if k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext.
symmetricstate_encrypt_and_hash
symmetricstate_encrypt_and_hash :: proc(self: ^Symmetric_State, plaintext: []u8, dst: []u8) -> ([]u8, Status)SourceSets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext), and returns ciphertext.
Note that if k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext.
symmetricstate_get_handshake_hash
symmetricstate_get_handshake_hash :: proc(self: ^Symmetric_State) -> ([]u8)SourceReturns h. This function should only be called at the end of a handshake, i.e. after the Split() function has been called.
This function is used for channel binding, as described in Section 11.2
symmetricstate_initialize
symmetricstate_initialize :: proc(ss: ^Symmetric_State, protocol_name: string) -> (Status)SourceTakes an arbitrary-length protocol_name byte sequence (see Section 8). Executes the following steps:
- If protocol_name is less than or equal to HASHLEN bytes in length,
sets h equal to protocol_name with zero bytes appended to make
HASHLEN bytes.
- Otherwise sets h = HASH(protocol_name).
- Sets ck = h.
- Calls InitializeKey(empty).symmetricstate_mix_hash
symmetricstate_mix_hash :: proc(self: ^Symmetric_State, data)SourceSets h = HASH(h || data).
symmetricstate_mix_key
symmetricstate_mix_key :: proc(self: ^Symmetric_State, input_key_material: []u8)SourceExecutes the following steps:
- Sets ck, temp_k = HKDF(ck, input_key_material, 2).
- If HASHLEN is 64, then truncates temp_k to 32 bytes.
- Calls InitializeKey(temp_k).
symmetricstate_mix_key_and_hash
symmetricstate_mix_key_and_hash :: proc(self: ^Symmetric_State, input_key_material: []u8)SourceThis function is used for handling pre-shared symmetric keys, as described in Section 9. It executes the following steps:
- Sets ck, temp_h, temp_k = HKDF(ck, input_key_material, 3).
- Calls MixHash(temp_h).
- If HASHLEN is 64, then truncates temp_k to 32 bytes.
- Calls InitializeKey(temp_k).
symmetricstate_reset
symmetricstate_reset :: proc(self: ^Symmetric_State)Sourcesymmetricstate_split
symmetricstate_split :: proc(self: ^Symmetric_State, cipher_states: ^Cipher_States)SourceReturns a pair of CipherState objects for encrypting transport messages. Executes the following steps, where zerolen is a zero-length byte sequence:
- Sets temp_k1, temp_k2 = HKDF(ck, zerolen, 2).
- If HASHLEN is 64, then truncates temp_k1 and temp_k2 to 32 bytes.
- Creates two new CipherState objects c1 and c2.
- Calls c1.InitializeKey(temp_k1) and c2.InitializeKey(temp_k2).
- Returns the pair (c1, c2).