mirror of
https://github.com/odin-lang/Odin.git
synced 2026-05-25 13:18:14 +00:00
990 lines
31 KiB
Odin
990 lines
31 KiB
Odin
#+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
|
||
}
|