Files
Odin/core/crypto/noise/protocol.odin
2026-05-06 09:53:02 +09:00

990 lines
31 KiB
Odin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+private
package noise
import "base:runtime"
import "core:crypto"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:crypto/hkdf"
import "core:encoding/endian"
import "core:slice"
AEAD_KEY_SIZE :: 32
MIN_DH_SIZE :: 32
MAX_DH_SIZE :: 56
MAX_HASH_SIZE :: 64
Protocol :: struct {
handshake_pattern: Handshake_Pattern,
dh: ecdh.Curve,
cipher: aead.Algorithm,
hash: hash.Algorithm,
}
Symmetric_State :: struct {
protocol: Protocol,
cipher_state: Cipher_State,
_ck: [MAX_HASH_SIZE]byte,
_h: [MAX_HASH_SIZE]byte,
}
Cipher_State :: struct {
ctx: aead.Context,
n: u64,
n_exhausted: bool,
is_invalid: bool,
}
@(require_results)
dh_len :: proc(protocol: ^Protocol) -> int {
return ecdh.PUBLIC_KEY_SIZES[protocol.dh]
}
@(require_results)
hash_len :: proc(protocol: ^Protocol) -> int {
return hash.DIGEST_SIZES[protocol.hash]
}
// Generates 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.
generate_keypair :: proc(protocol: ^Protocol, private_key: ^ecdh.Private_Key) {
#partial switch protocol.dh {
case .X25519, .X448:
case: panic("crypto/noise: unsupported DH curve in protocol")
}
if !ecdh.private_key_generate(private_key, protocol.dh) {
panic("crypto/noise: entropy source unavailable")
}
}
// Performs 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.
@(require_results)
_dh :: proc(our_private_key: ^ecdh.Private_Key, their_public_key: ^ecdh.Public_Key, dst: []byte) -> Status {
if ok := ecdh.ecdh(our_private_key, their_public_key, dst); !ok {
return .DH_Failure
}
return .Ok
}
// Encrypts 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).
_encrypt :: proc(ctx: ^aead.Context, n: u64, ad, plaintext, dst: []byte) {
pt_len := len(plaintext)
ensure(len(dst) == pt_len + TAG_SIZE, "crypto/noise: invalid AEAD encrypt destination")
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ciphertext, tag := dst[:pt_len], dst[pt_len:]
aead.seal_ctx(ctx, ciphertext, tag, iv[:], ad, plaintext)
}
// Decrypts 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.
@(require_results)
_decrypt :: proc(ctx: ^aead.Context, n: u64, ad, ciphertext, dst: []byte) -> Status {
if len(ciphertext) < TAG_SIZE {
return .Decryption_Failure
}
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ct_len := len(ciphertext) - TAG_SIZE
ct, tag := ciphertext[:ct_len], ciphertext[ct_len:]
if ok := aead.open_ctx(ctx, dst, iv[:], ad, ct, tag); !ok {
return .Decryption_Failure
}
return .Ok
}
// Hashes some arbitrary-length data with a collision-resistant cryptographic
// hash function and returns an output of HASHLEN bytes.
_hash :: proc(dst: []byte, protocol: ^Protocol, data: ..[]byte) {
ctx: hash.Context
hash.init(&ctx, protocol.hash)
for datum in data {
hash.update(&ctx, datum)
}
hash.final(&ctx, dst)
}
// Takes 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.
@(require_results)
_hkdf :: proc(dst, chaining_key, input_key_material: []byte, protocol: ^Protocol) -> ([]byte, []byte, []byte) {
assert(len(input_key_material) == 0 || len(input_key_material) == 32 || len(input_key_material) == dh_len(protocol))
hkdf.extract_and_expand(protocol.hash, chaining_key, input_key_material, nil, dst)
h_len := hash_len(protocol)
assert(len(dst) == h_len * 2 || len(dst) == h_len * 3)
r1, r2 := dst[:h_len], dst[h_len:h_len*2]
if len(dst) == h_len * 2 {
return r1, r2, nil
}
return r1, r2, dst[h_len*2:]
}
// Sets k = key. Sets n = 0.
cipherstate_initialize_key :: proc(self: ^Cipher_State, key: []byte, protocol: ^Protocol) {
k_len := len(key)
switch {
case k_len == 0:
// k = empty
aead.reset(&self.ctx)
self.n = 0
case k_len < AEAD_KEY_SIZE:
panic("crypto/noise: invalid AEAD key size")
case:
aead.init(&self.ctx, protocol.cipher, key[:AEAD_KEY_SIZE])
self.n = 0
}
}
// Returns true if k is non-empty, false otherwise.
@(require_results)
cipherstate_has_key :: proc(self: ^Cipher_State) -> bool {
return aead.algorithm(&self.ctx) != .Invalid
}
// If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise
// returns plaintext.
@(require_results)
cipherstate_encrypt_with_ad :: proc(self: ^Cipher_State, ad, plaintext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
pt_len := len(plaintext)
if pt_len > MAX_PACKET_SIZE - 16 {
return nil, .Max_Packet_Size
}
if cipherstate_has_key(self) {
if len(dst) != pt_len + TAG_SIZE {
return nil, .Invalid_Destination_Buffer
}
_encrypt(&self.ctx, self.n, ad, plaintext, dst)
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != pt_len {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(plaintext) {
copy(dst, plaintext)
}
}
return dst, .Ok
}
// If 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.
@(require_results)
cipherstate_decrypt_with_ad :: proc(self: ^Cipher_State, ad, ciphertext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
if cipherstate_has_key(self) {
if status := _decrypt(&self.ctx, self.n, ad, ciphertext, dst); status != .Ok {
return nil, status
}
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != len(ciphertext) {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(ciphertext) {
copy(dst, ciphertext)
}
}
return dst, .Ok
}
// Sets k = REKEY(k).
cipherstate_rekey :: proc(self: ^Cipher_State) {
if cipherstate_has_key(self) {
algorithm := aead.algorithm(&self.ctx)
// The "sensible" way to implement this is to inlike REKEY(k),
// so we do.
//
// Returns a new 32-byte cipher key as a pseudorandom function
// of k. If this function is not specifically defined for some
// set of cipher functions, then it defaults to returning the
// first 32 bytes from `ENCRYPT(k, maxnonce, zerolen, zeros)`,
// where maxnonce equals (2^64)-1, zerolen is a zero-length
// byte sequence, and zeros is a sequence of 32 bytes filled
// with zeros.
zeroes: [AEAD_KEY_SIZE + TAG_SIZE]byte
defer crypto.zero_explicit(&zeroes, size_of(zeroes))
// 1 2 3 4 5 6 7 8
n: u64 = 0xFF_FF_FF_FF_FF_FF_FF_FF
_encrypt(&self.ctx, n, nil, zeroes[:AEAD_KEY_SIZE], zeroes[:])
aead.init(&self.ctx, algorithm, zeroes[:AEAD_KEY_SIZE])
}
}
cipherstate_reset :: proc(self: ^Cipher_State) {
aead.reset(&self.ctx)
crypto.zero_explicit(self, size_of(Cipher_State))
}
// Takes 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).
@(require_results)
symmetricstate_initialize :: proc(ss: ^Symmetric_State, protocol_name: string) -> Status {
if status := protocol_from_string(&ss.protocol, protocol_name); status != .Ok {
return status
}
cipherstate_initialize_key(&ss.cipher_state, nil, &ss.protocol)
h_len := hash_len(&ss.protocol)
h := ss._h[:h_len]
if len(protocol_name) <= h_len {
copy(h, protocol_name)
} else {
_hash(h, &ss.protocol, transmute([]byte)protocol_name)
}
copy(ss._ck[:h_len], h)
return .Ok
}
// Sets h = HASH(h || data).
symmetricstate_mix_hash :: proc(self: ^Symmetric_State, data: ..[]byte) {
h := self._h[:hash_len(&self.protocol)]
if len(data) == 1 {
_hash(h, &self.protocol, h, data[0])
} else if len(data) == 2 {
_hash(h, &self.protocol, h, data[0], data[1])
} else if len(data) == 3 {
_hash(h, &self.protocol, h, data[0], data[1], data[2])
} else {
panic("crypto/noise: invalid MixHash inputs")
}
}
// Executes 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 :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_k, _ := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// This 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_mix_key_and_hash :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 3
dst: [3*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_h, temp_k := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
symmetricstate_mix_hash(self, temp_h)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// Returns 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
@(require_results)
symmetricstate_get_handshake_hash :: proc(self: ^Symmetric_State) -> []byte {
return self._h[:hash_len(&self.protocol)]
}
// Sets 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.
@(require_results)
symmetricstate_encrypt_and_hash :: proc(self: ^Symmetric_State, plaintext, dst: []byte) -> ([]byte, Status) {
ciphertext, status := cipherstate_encrypt_with_ad(&self.cipher_state, self._h[:hash_len(&self.protocol)], plaintext, dst)
if status != .Ok {
return nil, status
}
symmetricstate_mix_hash(self, ciphertext)
return ciphertext, status
}
// Sets 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.
@(require_results)
symmetricstate_decrypt_and_hash :: proc(self: ^Symmetric_State, ciphertext, dst: []byte) -> ([]byte, Status) {
h_len := hash_len(&self.protocol)
h: [MAX_HASH_SIZE]byte = ---
copy(h[:], self._h[:h_len])
defer crypto.zero_explicit(&h, size_of(h))
// We reverse the order to save having to copy the ciphertext, in
// the case that ciphertext and dst alias.
symmetricstate_mix_hash(self, ciphertext)
return cipherstate_decrypt_with_ad(&self.cipher_state, h[:h_len], ciphertext, dst)
}
// Returns 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).
symmetricstate_split :: proc(self: ^Symmetric_State, cipher_states: ^Cipher_States) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
temp_k1, temp_k2, _ := _hkdf(dst[:dst_len], self._ck[:h_len], nil, &self.protocol)
cipherstate_initialize_key(&cipher_states.c1_i_to_r, temp_k1, &self.protocol)
cipherstate_initialize_key(&cipher_states.c2_r_to_i, temp_k2, &self.protocol)
}
symmetricstate_reset :: proc(self: ^Symmetric_State) {
cipherstate_reset(&self.cipher_state)
crypto.zero_explicit(self, size_of(Symmetric_State))
}
// Takes 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.
@(require_results)
handshakestate_initialize :: proc(
handshake_state: ^Handshake_State,
initiator: bool,
prologue: []byte,
s: ^ecdh.Private_Key,
e: ^ecdh.Private_Key, // Only set for testing.
rs: ^ecdh.Public_Key,
re: ^ecdh.Public_Key, // Only set for testing.
protocol_name: string,
psk: []byte = nil,
) -> Status {
crypto.zero_explicit(handshake_state, size_of(Handshake_State))
symmetric_state := &handshake_state.symmetric_state
status: Status
do_init: {
if status = symmetricstate_initialize(symmetric_state, protocol_name); status != .Ok {
break do_init
}
curve := symmetric_state.protocol.dh
if s != nil && ecdh.curve(s) != curve {
status = .Invalid_DH_Key
break do_init
}
if e != nil && ecdh.curve(e) != curve {
status = .Invalid_DH_Key
break do_init
}
if rs != nil && ecdh.curve(rs) != curve {
status = .Invalid_DH_Key
break do_init
}
if re != nil && ecdh.curve(re) != curve {
status = .Invalid_DH_Key
break do_init
}
// Check if we will require s later down the line.
s_pre, s_hs: bool
if initiator {
s_pre, s_hs = pattern_requires_initiator_s(symmetric_state.protocol.handshake_pattern)
} else {
s_pre, s_hs = pattern_requires_responder_s(symmetric_state.protocol.handshake_pattern)
}
if (s_pre || s_hs) && s == nil {
status = .No_Self_Identity
break do_init
}
message_pattern := HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
if message_pattern.pre_messages != nil {
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
}
} else {
if rs != nil {
status = .Unexpected_Peer_Identity
break do_init
}
}
symmetricstate_mix_hash(symmetric_state, prologue)
// In all supported patterns, `ini_s` will always precede `res_s`.
if message_pattern.pre_messages != nil {
tmp: [MAX_DH_SIZE]byte = ---
d_len := dh_len(&symmetric_state.protocol)
dst := tmp[:d_len]
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.public_key_bytes(&s._pub_key, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.public_key_bytes(&s._pub_key, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
}
}
if message_pattern.is_psk {
if len(psk) != PSK_SIZE {
status = .Invalid_Pre_Shared_Key
break do_init
}
} else if len(psk) != 0 {
status = .Unexpected_Pre_Shared_Key
break do_init
}
}
if status != .Ok {
symmetricstate_reset(symmetric_state)
return status
}
if s != nil {
ecdh.private_key_set(&handshake_state.s, s)
}
if e != nil {
ecdh.private_key_set(&handshake_state.e, e)
handshake_state.pre_set_e = true
}
if rs != nil {
ecdh.public_key_set(&handshake_state.rs, rs)
}
if re != nil {
ecdh.public_key_set(&handshake_state.re, re)
}
copy(handshake_state.psk[:], psk)
handshake_state.message_pattern = HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
handshake_state.current_message = 0
handshake_state.status = .Handshake_Pending
handshake_state.initiator = initiator
return .Ok
}
handshakestate_reset :: proc(self: ^Handshake_State) {
symmetricstate_reset(&self.symmetric_state)
ecdh.private_key_clear(&self.s)
ecdh.private_key_clear(&self.e)
crypto.zero_explicit(self, size_of(Handshake_State))
}
// Takes 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.
@(require_results)
handshakestate_write_message :: proc(self: ^Handshake_State, payload, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for WriteMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(protocol)
pattern_buf: [dynamic; MAX_STEP_MSG_SIZE]byte
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
switch self.pre_set_e {
case true:
// Note: "which must be empty", but we allow pre-generated `e`
// for testing/rng-less systems.
self.pre_set_e = false
case false:
if ecdh.curve(&self.e) != .Invalid {
panic("crypto/noise: e was not empty when processing token 'e' during WriteMessage")
}
generate_keypair(protocol, &self.e)
}
e_public := dh_buf[:d_len]
ecdh.public_key_bytes(&self.e._pub_key, e_public)
n := append(&pattern_buf, ..e_public)
ensure(n == d_len, "crypto/noise: truncated append `e`")
symmetricstate_mix_hash(&self.symmetric_state, e_public)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, e_public)
}
case .s:
s_public := dh_buf[:d_len]
ecdh.public_key_bytes(&self.s._pub_key, s_public)
tmp: [MAX_DH_SIZE+TAG_SIZE]byte = ---
dh_buf := tmp[:d_len+TAG_SIZE]
if !cipherstate_has_key(&self.symmetric_state.cipher_state) {
dh_buf = tmp[:d_len]
}
ct, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, s_public, dh_buf)
if status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
n := append(&pattern_buf, ..ct)
ensure(n == len(ct), "crypto/noise: truncated append `s`")
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
pattern_len := len(pattern_buf)
payload_len := len(payload)
msg_len := pattern_len + payload_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
msg_len += TAG_SIZE
}
msg: []byte
if msg_len != 0 {
did_alloc: bool
if dst != nil {
if len(dst) < msg_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
msg = dst[:msg_len]
} else {
err: runtime.Allocator_Error
msg, err = make([]byte, msg_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
copy(msg, pattern_buf[:])
ciphertext := msg[pattern_len:]
if _, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, payload, ciphertext); status != .Ok {
if did_alloc {
delete(msg)
}
self.status = .Handshake_Failed
return nil, status
}
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return msg, self.status
}
// Takes 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.
@(require_results)
handshakestate_read_message :: proc(self: ^Handshake_State, message, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for ReadMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(&self.symmetric_state.protocol)
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
msg := message
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
if len(msg) < d_len {
return nil, .Invalid_Handshake_Message
}
re := msg[:d_len]
if ecdh.curve(&self.re) != .Invalid {
panic("crypto/noise: re was not empty when processing token 'e' during ReadMessage")
}
if !ecdh.public_key_set_bytes(&self.re, protocol.dh, re) {
return nil, .Invalid_Handshake_Message
}
symmetricstate_mix_hash(&self.symmetric_state, re)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, re)
}
msg = msg[d_len:]
case .s:
rs_len := d_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
rs_len += TAG_SIZE
}
if len(msg) < rs_len {
self.status = .Handshake_Failed
return nil, .Invalid_Handshake_Message
}
rs := dh_buf[:d_len]
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg[:rs_len], rs); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
if ecdh.curve(&self.rs) != .Invalid {
panic("crypto/noise: rs was not empty when processing token 's' during ReadMessage")
}
if !ecdh.public_key_set_bytes(&self.rs, protocol.dh, rs) {
self.status = .Handshake_Failed
return nil, .Invalid_Handshake_Message
}
msg = msg[rs_len:]
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
payload: []byte
payload_len := len(msg)
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
if payload_len < TAG_SIZE {
self.status = .Handshake_Failed
return nil, self.status
}
payload_len -= TAG_SIZE
}
did_alloc: bool
if dst != nil {
if len(dst) < payload_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
payload = dst[:payload_len]
} else if payload_len > 0 {
err: runtime.Allocator_Error
payload, err = make([]byte, payload_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg, payload); status != .Ok {
if did_alloc {
delete(payload)
}
self.status = .Handshake_Failed
return nil, self.status
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return payload, self.status
}
@(require_results)
protocol_from_string :: proc(self: ^Protocol, protocol_name: string) -> Status {
self^ = Protocol{}
pattern, dh, cipher, hash, status := split_protocol_string(protocol_name)
if status != .Ok {
return status
}
self.handshake_pattern = pattern
self.dh = dh
self.cipher = cipher
self.hash = hash
return .Ok
}