mirror of
https://github.com/odin-lang/Odin.git
synced 2026-05-29 15:15:08 +00:00
Added noise package to core:crypto
Special thanks to Yawning for review, guidance, and massive updates.
This commit is contained in:
committed by
Yawning Angel
parent
19f249b475
commit
2623a8fa9f
429
core/crypto/noise/api.odin
Normal file
429
core/crypto/noise/api.odin
Normal file
@@ -0,0 +1,429 @@
|
||||
package noise
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/ecdh"
|
||||
|
||||
// MAX_PACKET_SIZE is the maximum Noise message size, including TAG_SIZE
|
||||
// if relevant (`seal_message`, `open_message`).
|
||||
MAX_PACKET_SIZE :: 65535
|
||||
|
||||
// PSK_SIZE is the size of an optional handshake pre-shared symmetric key.
|
||||
PSK_SIZE :: 32
|
||||
// TAG_SIZE is the size of the AEAD authentication tag.
|
||||
TAG_SIZE :: 16
|
||||
// MAX_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.
|
||||
MAX_STEP_MSG_SIZE :: (MAX_DH_SIZE*2)+TAG_SIZE
|
||||
|
||||
// Status is the status of Noise protocol operation.
|
||||
Status :: enum {
|
||||
Ok,
|
||||
|
||||
// States
|
||||
Handshake_Pending,
|
||||
Handshake_Complete,
|
||||
Handshake_Split,
|
||||
Handshake_Failed,
|
||||
|
||||
// Errors
|
||||
Invalid_Protocol_String,
|
||||
Invalid_Pre_Shared_Key,
|
||||
Invalid_DH_Key,
|
||||
No_Self_Identity,
|
||||
No_Peer_Identity,
|
||||
Unexpected_Peer_Identity,
|
||||
Unexpected_Pre_Shared_Key,
|
||||
|
||||
DH_Failure,
|
||||
Invalid_Handshake_Message,
|
||||
|
||||
Decryption_Failure,
|
||||
IV_Exhausted,
|
||||
Invalid_Cipher_State,
|
||||
Invalid_Destination_Buffer,
|
||||
Invalid_Payload_Message,
|
||||
Max_Packet_Size,
|
||||
|
||||
Out_Of_Memory,
|
||||
}
|
||||
|
||||
// Handshake_State is the per-handshake state.
|
||||
Handshake_State :: struct {
|
||||
s: ecdh.Private_Key,
|
||||
e: ecdh.Private_Key,
|
||||
rs: ecdh.Public_Key,
|
||||
re: ecdh.Public_Key,
|
||||
psk: [PSK_SIZE]byte,
|
||||
|
||||
symmetric_state: Symmetric_State,
|
||||
message_pattern: ^Message_Pattern,
|
||||
current_message: int,
|
||||
|
||||
status: Status,
|
||||
|
||||
initiator: bool,
|
||||
pre_set_e: bool,
|
||||
}
|
||||
|
||||
// Cipher_States are the keyed AEAD instances and associated state,
|
||||
// derived from a successful handshake.
|
||||
Cipher_States :: struct {
|
||||
c1_i_to_r: Cipher_State,
|
||||
c2_r_to_i: Cipher_State,
|
||||
|
||||
initiator: bool,
|
||||
}
|
||||
|
||||
// handshake_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.
|
||||
@(require_results)
|
||||
handshake_init :: proc(
|
||||
self: ^Handshake_State,
|
||||
initiator: bool,
|
||||
prologue: []byte,
|
||||
s: ^ecdh.Private_Key, // Our static key
|
||||
rs: ^ecdh.Public_Key, // Peer static key
|
||||
protocol_name: string,
|
||||
psk: []byte = nil,
|
||||
_e: ^ecdh.Private_Key = nil, // Our ephemeral key (for testing/RNG-less systems)
|
||||
) -> Status {
|
||||
return handshakestate_Initialize(
|
||||
self,
|
||||
initiator,
|
||||
prologue,
|
||||
s,
|
||||
_e,
|
||||
rs,
|
||||
nil,
|
||||
protocol_name,
|
||||
psk,
|
||||
)
|
||||
}
|
||||
|
||||
// handshake_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.
|
||||
@(require_results)
|
||||
handshake_initiator_step :: proc(
|
||||
self: ^Handshake_State,
|
||||
input_message: []byte,
|
||||
payload: []byte = nil,
|
||||
dst: []byte = nil,
|
||||
allocator := context.allocator,
|
||||
) -> ([]byte, []byte, Status) {
|
||||
output_message: []byte
|
||||
payload_buffer: []byte
|
||||
status: Status
|
||||
|
||||
dst := dst
|
||||
if input_message == nil {
|
||||
output_message, status = handshakestate_WriteMessage(self, payload, dst, allocator)
|
||||
} else {
|
||||
payload_buffer, status = handshakestate_ReadMessage(self, input_message, dst, allocator)
|
||||
if status == .Handshake_Pending {
|
||||
if dst != nil {
|
||||
dst = dst[len(payload_buffer):]
|
||||
}
|
||||
output_message, status = handshakestate_WriteMessage(self, payload, dst, allocator)
|
||||
}
|
||||
}
|
||||
|
||||
return output_message, payload_buffer, status
|
||||
}
|
||||
|
||||
// handshake_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.
|
||||
@(require_results)
|
||||
handshake_responder_step :: proc(
|
||||
self: ^Handshake_State,
|
||||
input_message: []byte,
|
||||
payload: []byte = nil,
|
||||
dst: []byte = nil,
|
||||
allocator := context.allocator,
|
||||
) -> ([]byte, []byte, Status) {
|
||||
output_message: []byte
|
||||
|
||||
if input_message == nil {
|
||||
return nil, nil, .Invalid_Handshake_Message
|
||||
}
|
||||
|
||||
dst := dst
|
||||
payload_buffer, status := handshakestate_ReadMessage(self, input_message, dst, allocator)
|
||||
if status == .Handshake_Pending {
|
||||
if dst != nil {
|
||||
dst = dst[len(payload_buffer):]
|
||||
}
|
||||
output_message, status = handshakestate_WriteMessage(self, payload, dst, allocator)
|
||||
}
|
||||
|
||||
return output_message, payload_buffer, status
|
||||
}
|
||||
|
||||
// handshake_split initializes a Cipher_States instance from a completed
|
||||
// handshake. This can be called once and only once per Handshake_State
|
||||
// instance.
|
||||
@(require_results)
|
||||
handshake_split :: proc(self: ^Handshake_State, cipher_states: ^Cipher_States) -> Status {
|
||||
if self.status != .Handshake_Complete {
|
||||
return self.status
|
||||
}
|
||||
|
||||
symmetricstate_Split(&self.symmetric_state, cipher_states)
|
||||
if self.message_pattern.is_one_way {
|
||||
cipherstate_reset(&cipher_states.c2_r_to_i)
|
||||
cipher_states.c2_r_to_i.is_invalid = true
|
||||
}
|
||||
cipher_states.initiator = self.initiator
|
||||
self.status = .Handshake_Split
|
||||
|
||||
return .Ok
|
||||
}
|
||||
|
||||
// handshake_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.
|
||||
@(require_results)
|
||||
handshake_peer_identity :: proc(self: ^Handshake_State) -> (^ecdh.Public_Key, Status) {
|
||||
#partial switch self.status {
|
||||
case .Handshake_Complete, .Handshake_Split:
|
||||
case:
|
||||
return nil, self.status
|
||||
}
|
||||
|
||||
if ecdh.curve(&self.rs) == .Invalid {
|
||||
return nil, .No_Peer_Identity
|
||||
}
|
||||
|
||||
return &self.rs, .Ok
|
||||
}
|
||||
|
||||
// handshake_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.
|
||||
@(require_results)
|
||||
handshake_hash :: proc(self: ^Handshake_State) -> ([]byte, Status) {
|
||||
#partial switch self.status {
|
||||
case .Handshake_Complete, .Handshake_Split:
|
||||
case:
|
||||
return nil, self.status
|
||||
}
|
||||
|
||||
return symmetricstate_GetHandshakeHash(&self.symmetric_state), .Ok
|
||||
}
|
||||
|
||||
// handshake_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_reset :: proc(self: ^Handshake_State) {
|
||||
handshakestate_reset(self)
|
||||
}
|
||||
|
||||
// seal_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.
|
||||
@(require_results)
|
||||
seal_message :: proc(self: ^Cipher_States, aad, plaintext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
|
||||
data_len := len(plaintext)
|
||||
|
||||
dst := dst
|
||||
did_alloc: bool
|
||||
switch {
|
||||
case dst == nil:
|
||||
err: runtime.Allocator_Error
|
||||
dst, err = make([]byte, data_len + TAG_SIZE, allocator)
|
||||
if err != nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
did_alloc = true
|
||||
case:
|
||||
if len(dst) != data_len + TAG_SIZE {
|
||||
return nil, .Invalid_Destination_Buffer
|
||||
}
|
||||
}
|
||||
|
||||
status: Status
|
||||
switch self.initiator {
|
||||
case true:
|
||||
dst, status = cipherstate_EncryptWithAd(&self.c1_i_to_r, aad, plaintext, dst)
|
||||
case false:
|
||||
dst, status = cipherstate_EncryptWithAd(&self.c2_r_to_i, aad, plaintext, dst)
|
||||
}
|
||||
if status != .Ok && did_alloc {
|
||||
delete(dst, allocator)
|
||||
dst = nil
|
||||
}
|
||||
|
||||
return dst, status
|
||||
}
|
||||
|
||||
// open_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.
|
||||
@(require_results)
|
||||
open_message :: proc(self: ^Cipher_States, aad, ciphertext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
|
||||
if len(ciphertext) < TAG_SIZE {
|
||||
return nil, .Invalid_Payload_Message
|
||||
}
|
||||
|
||||
data_len := len(ciphertext) - TAG_SIZE
|
||||
|
||||
dst := dst
|
||||
did_alloc: bool
|
||||
switch {
|
||||
case dst == nil:
|
||||
if data_len > 0 {
|
||||
err: runtime.Allocator_Error
|
||||
dst, err = make([]byte, data_len, allocator)
|
||||
if err != nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
did_alloc = true
|
||||
}
|
||||
case:
|
||||
if len(dst) != data_len {
|
||||
return nil, .Invalid_Destination_Buffer
|
||||
}
|
||||
}
|
||||
|
||||
status: Status
|
||||
switch self.initiator {
|
||||
case true:
|
||||
dst, status = cipherstate_DecryptWithAd(&self.c2_r_to_i, aad, ciphertext, dst)
|
||||
case false:
|
||||
dst, status = cipherstate_DecryptWithAd(&self.c1_i_to_r, aad, ciphertext, dst)
|
||||
}
|
||||
if status != .Ok && did_alloc {
|
||||
delete(dst, allocator)
|
||||
dst = nil
|
||||
}
|
||||
|
||||
return dst, status
|
||||
}
|
||||
|
||||
// cipherstates_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.
|
||||
@(require_results)
|
||||
cipherstates_rekey :: proc(self: ^Cipher_States, seal_key: bool) -> Status {
|
||||
cs := cipherstates_cs(self, seal_key)
|
||||
if cs.is_invalid {
|
||||
return .Invalid_Cipher_State
|
||||
}
|
||||
if !cipherstate_HasKey(cs) {
|
||||
return .Handshake_Pending
|
||||
}
|
||||
|
||||
cipherstate_Rekey(cs)
|
||||
|
||||
return .Ok
|
||||
}
|
||||
|
||||
// cipherstates_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.
|
||||
@(require_results)
|
||||
cipherstates_set_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> Status {
|
||||
cs := cipherstates_cs(self, seal_key)
|
||||
if cs.is_invalid {
|
||||
return .Invalid_Cipher_State
|
||||
}
|
||||
if !cipherstate_HasKey(cs) {
|
||||
return .Handshake_Pending
|
||||
}
|
||||
|
||||
cs.n = n
|
||||
|
||||
return .Ok
|
||||
}
|
||||
|
||||
// cipherstates_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.
|
||||
@(require_results)
|
||||
cipherstates_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> (u64, Status) {
|
||||
cs := cipherstates_cs(self, seal_key)
|
||||
if cs.is_invalid {
|
||||
return 0, .Invalid_Cipher_State
|
||||
}
|
||||
if !cipherstate_HasKey(cs) {
|
||||
return 0, .Handshake_Pending
|
||||
}
|
||||
|
||||
return cs.n, .Ok
|
||||
}
|
||||
|
||||
// cipherstates_reset sanitizes the Cipher_States.
|
||||
cipherstates_reset :: proc(self: ^Cipher_States) {
|
||||
self.initiator = false
|
||||
cipherstate_reset(&self.c1_i_to_r)
|
||||
cipherstate_reset(&self.c2_r_to_i)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
cipherstates_cs :: proc(self: ^Cipher_States, seal_key: bool) -> ^Cipher_State {
|
||||
switch self.initiator {
|
||||
case true:
|
||||
switch seal_key {
|
||||
case true:
|
||||
return &self.c1_i_to_r
|
||||
case false:
|
||||
return &self.c2_r_to_i
|
||||
}
|
||||
case false:
|
||||
switch seal_key {
|
||||
case true:
|
||||
return &self.c2_r_to_i
|
||||
case false:
|
||||
return &self.c1_i_to_r
|
||||
}
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
35
core/crypto/noise/doc.odin
Normal file
35
core/crypto/noise/doc.odin
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
An implementation of the Noise Protocol Framework (Revision 34).
|
||||
|
||||
The `fallback` modifier and deferred/multi-PSK patterns are not supported
|
||||
for the sake of simplicity.
|
||||
|
||||
See:
|
||||
- [[ https://noiseprotocol.org/ ]]
|
||||
*/
|
||||
package noise
|
||||
|
||||
// In general, to complete a noise handshake you must:
|
||||
//
|
||||
// - If you are initiating the connection, call `handshake_initiator_step`
|
||||
// passing `nil` as the `input_message` parameter.
|
||||
//
|
||||
// - Send the resulting `[]byte` to the responder (generally a server) via
|
||||
// the method of your choice. This MUST be done even if the status code
|
||||
// returned is `.Handshake_Complete`.
|
||||
//
|
||||
// - If the status code returned by `handshake_initiator_step` was
|
||||
// `.Handshake_Complete`, the handshake completed successfully,
|
||||
// and it is now possible to validate the peer identity, obtain the
|
||||
// handshake transcript hash, and most usefully call `handshake_split`
|
||||
// to populate the `Cipher_States` struct that will be used to
|
||||
// encrypt/decrypt data.
|
||||
//
|
||||
// Otherwise, read the response from the responder and feed the response
|
||||
// data as the `input_message` to the next `handshake_initiator_step`
|
||||
// until it returns `.Handshake_Complete`.
|
||||
//
|
||||
// - If you are the responder, the method is much the same, except you
|
||||
// must pass a valid `input_message` received from an initiator to the
|
||||
// first call to `handshake_responder_step`. Repeat until the returned
|
||||
// status is `.Handshake_Complete`.
|
||||
686
core/crypto/noise/patterns.odin
Normal file
686
core/crypto/noise/patterns.odin
Normal file
@@ -0,0 +1,686 @@
|
||||
package noise
|
||||
|
||||
import "core:slice"
|
||||
|
||||
@(private)
|
||||
Pre_Token :: enum {
|
||||
res_s,
|
||||
ini_s,
|
||||
}
|
||||
|
||||
@(private)
|
||||
Token :: enum {
|
||||
e,
|
||||
s,
|
||||
ee,
|
||||
es,
|
||||
se,
|
||||
ss,
|
||||
psk,
|
||||
}
|
||||
|
||||
@(private)
|
||||
Message_Pattern :: struct {
|
||||
pre_messages: []Pre_Token,
|
||||
messages: [][]Token,
|
||||
is_psk: bool,
|
||||
is_one_way: bool,
|
||||
}
|
||||
|
||||
// Handshake_Pattern is the list of currently supported Noise Handshake
|
||||
// Patterns.
|
||||
Handshake_Pattern :: enum {
|
||||
Invalid,
|
||||
|
||||
// One way patterns
|
||||
N,
|
||||
K,
|
||||
X,
|
||||
|
||||
// Fundamental patterns
|
||||
XX,
|
||||
NK,
|
||||
NN,
|
||||
KN,
|
||||
KK,
|
||||
NX,
|
||||
KX,
|
||||
XN,
|
||||
IN,
|
||||
XK,
|
||||
IK,
|
||||
IX,
|
||||
|
||||
// Recommended PSK patterns
|
||||
Npsk0,
|
||||
Kpsk0,
|
||||
Xpsk1,
|
||||
NNpsk0,
|
||||
NNpsk2,
|
||||
NKpsk0,
|
||||
NKpsk2,
|
||||
NXpsk2,
|
||||
XNpsk3,
|
||||
XKpsk3,
|
||||
XXpsk3,
|
||||
KNpsk0,
|
||||
KNpsk2,
|
||||
KKpsk0,
|
||||
KKpsk2,
|
||||
KXpsk2,
|
||||
INpsk1,
|
||||
INpsk2,
|
||||
IKpsk1,
|
||||
IKpsk2,
|
||||
IXpsk2,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pattern_requires_initiator_s :: proc(pattern: Handshake_Pattern) -> (pre: bool, hs: bool) {
|
||||
p := HANDSHAKE_PATTERNS[pattern]
|
||||
if slice.contains(p.pre_messages, Pre_Token.ini_s) {
|
||||
pre = true
|
||||
}
|
||||
for msg, i in p.messages {
|
||||
if i & 1 != 0 {
|
||||
continue
|
||||
}
|
||||
if slice.contains(msg, Token.s) {
|
||||
hs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return pre, hs
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pattern_requires_responder_s :: proc(pattern: Handshake_Pattern) -> (pre: bool, hs: bool) {
|
||||
p := HANDSHAKE_PATTERNS[pattern]
|
||||
if slice.contains(p.pre_messages, Pre_Token.res_s) {
|
||||
pre = true
|
||||
}
|
||||
for msg, i in p.messages {
|
||||
if i & 1 == 0 {
|
||||
continue
|
||||
}
|
||||
if slice.contains(msg, Token.s) {
|
||||
hs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return pre, hs
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pattern_is_psk :: proc(pattern: Handshake_Pattern) -> bool {
|
||||
return HANDSHAKE_PATTERNS[pattern].is_psk
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pattern_is_one_way :: proc(pattern: Handshake_Pattern) -> bool {
|
||||
return HANDSHAKE_PATTERNS[pattern].is_one_way
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pattern_num_messages :: proc(pattern: Handshake_Pattern) -> int {
|
||||
return len(HANDSHAKE_PATTERNS[pattern].messages)
|
||||
}
|
||||
|
||||
@(private)
|
||||
HANDSHAKE_PATTERNS := [Handshake_Pattern]^Message_Pattern {
|
||||
.Invalid = nil,
|
||||
.N = &PATTERN_N,
|
||||
.K = &PATTERN_K,
|
||||
.X = &PATTERN_X,
|
||||
.XX = &PATTERN_XX,
|
||||
.NK = &PATTERN_NK,
|
||||
.NN = &PATTERN_NN,
|
||||
.KN = &PATTERN_KN,
|
||||
.KK = &PATTERN_KK,
|
||||
.NX = &PATTERN_NX,
|
||||
.KX = &PATTERN_KX,
|
||||
.XN = &PATTERN_XN,
|
||||
.IN = &PATTERN_IN,
|
||||
.XK = &PATTERN_XK,
|
||||
.IK = &PATTERN_IK,
|
||||
.IX = &PATTERN_IX,
|
||||
.Npsk0 = &PATTERN_Npsk0,
|
||||
.Kpsk0 = &PATTERN_Kpsk0,
|
||||
.Xpsk1 = &PATTERN_Xpsk1,
|
||||
.NNpsk0 = &PATTERN_NNpsk0,
|
||||
.NNpsk2 = &PATTERN_NNpsk2,
|
||||
.NKpsk0 = &PATTERN_NKpsk0,
|
||||
.NKpsk2 = &PATTERN_NKpsk2,
|
||||
.NXpsk2 = &PATTERN_NXpsk2,
|
||||
.XNpsk3 = &PATTERN_XNpsk3,
|
||||
.XKpsk3 = &PATTERN_XKpsk3,
|
||||
.XXpsk3 = &PATTERN_XXpsk3,
|
||||
.KNpsk0 = &PATTERN_KNpsk0,
|
||||
.KNpsk2 = &PATTERN_KNpsk2,
|
||||
.KKpsk0 = &PATTERN_KKpsk0,
|
||||
.KKpsk2 = &PATTERN_KKpsk2,
|
||||
.KXpsk2 = &PATTERN_KXpsk2,
|
||||
.INpsk1 = &PATTERN_INpsk1,
|
||||
.INpsk2 = &PATTERN_INpsk2,
|
||||
.IKpsk1 = &PATTERN_IKpsk1,
|
||||
.IKpsk2 = &PATTERN_IKpsk2,
|
||||
.IXpsk2 = &PATTERN_IXpsk2,
|
||||
}
|
||||
|
||||
// ------------- ONE WAY PATTERNS ---------------------------------------------------------
|
||||
|
||||
// N:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es
|
||||
@(private,rodata)
|
||||
PATTERN_N : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es},
|
||||
},
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// K:
|
||||
// -> s
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, ss
|
||||
@(private,rodata)
|
||||
PATTERN_K : Message_Pattern = {
|
||||
pre_messages = {.ini_s, .res_s},
|
||||
messages = {
|
||||
{.e, .es, .ss},
|
||||
},
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// X:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, s, ss
|
||||
@(private,rodata)
|
||||
PATTERN_X : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es, .s, .ss},
|
||||
},
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
// ------------- FUNDAMENTAL PATTERNS -----------------------------------------------------
|
||||
|
||||
// XX:
|
||||
// -> e
|
||||
// <- e, ee, s, es
|
||||
// -> s, se
|
||||
@(private,rodata)
|
||||
PATTERN_XX : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .s, .es},
|
||||
{.s, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// NK:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es
|
||||
// <- e, ee
|
||||
@(private,rodata)
|
||||
PATTERN_NK : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es},
|
||||
{.e, .ee},
|
||||
},
|
||||
}
|
||||
|
||||
// NN:
|
||||
// -> e
|
||||
// <- e, ee
|
||||
@(private,rodata)
|
||||
PATTERN_NN : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee},
|
||||
},
|
||||
}
|
||||
|
||||
// KN:
|
||||
// -> s
|
||||
// ...
|
||||
// -> e
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_KN : Message_Pattern = {
|
||||
pre_messages = {.ini_s},
|
||||
messages = {
|
||||
{.e,},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// KK:
|
||||
// -> s
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, ss
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_KK : Message_Pattern = {
|
||||
pre_messages = {.ini_s, .res_s},
|
||||
messages = {
|
||||
{.e, .es, .ss},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// NX:
|
||||
// -> e
|
||||
// <- e, ee, s, es
|
||||
@(private,rodata)
|
||||
PATTERN_NX : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .s, .es},
|
||||
},
|
||||
}
|
||||
|
||||
// KX:
|
||||
// -> s
|
||||
// ...
|
||||
// -> e
|
||||
// <- e, ee, se, s, es
|
||||
@(private,rodata)
|
||||
PATTERN_KX : Message_Pattern = {
|
||||
pre_messages = {.ini_s},
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .se, .s, .es},
|
||||
},
|
||||
}
|
||||
|
||||
// XN:
|
||||
// -> e
|
||||
// <- e, ee
|
||||
// -> s, se
|
||||
@(private,rodata)
|
||||
PATTERN_XN : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee},
|
||||
{.s, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// IN:
|
||||
// -> e, s
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_IN : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e, .s},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// XK:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es
|
||||
// <- e, ee
|
||||
// -> s, se
|
||||
@(private,rodata)
|
||||
PATTERN_XK : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es},
|
||||
{.e, .ee},
|
||||
{.s, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// IK:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, s, ss
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_IK : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es, .s, .ss},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
}
|
||||
|
||||
// IX:
|
||||
// -> e, s
|
||||
// <- e, ee, se, s, es
|
||||
@(private,rodata)
|
||||
PATTERN_IX : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e, .s},
|
||||
{.e, .ee, .se, .s, .es},
|
||||
},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
// ------------- PSK PATTERNS -------------------------------------------------------------
|
||||
|
||||
// Npsk0:
|
||||
// <- s
|
||||
// ...
|
||||
// -> psk, e, es
|
||||
@(private,rodata)
|
||||
PATTERN_Npsk0 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.psk, .e, .es},
|
||||
},
|
||||
is_psk = true,
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// K:
|
||||
// -> s
|
||||
// <- s
|
||||
// ...
|
||||
// -> psk, e, es, ss
|
||||
@(private,rodata)
|
||||
PATTERN_Kpsk0 : Message_Pattern = {
|
||||
pre_messages = {.ini_s, .res_s},
|
||||
messages = {
|
||||
{.psk, .e, .es, .ss},
|
||||
},
|
||||
is_psk = true,
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// X:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, s, ss, psk
|
||||
@(private,rodata)
|
||||
PATTERN_Xpsk1 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es, .s, .ss, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
is_one_way = true,
|
||||
}
|
||||
|
||||
// NNpsk0:
|
||||
// -> psk, e
|
||||
// <- e, ee
|
||||
@(private,rodata)
|
||||
PATTERN_NNpsk0 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.psk, .e},
|
||||
{.e, .ee},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// NNpsk2:
|
||||
// -> e
|
||||
// <- e, ee, psk
|
||||
@(private,rodata)
|
||||
PATTERN_NNpsk2 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// NKpsk0:
|
||||
// <- s
|
||||
// ...
|
||||
// -> psk, e, es
|
||||
// <- e, ee
|
||||
@(private,rodata)
|
||||
PATTERN_NKpsk0 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.psk, .e, .es},
|
||||
{.e, .ee},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// NKpsk2:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es
|
||||
// <- e, ee, psk
|
||||
@(private,rodata)
|
||||
PATTERN_NKpsk2 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es},
|
||||
{.e, .ee, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// NXpsk2:
|
||||
// -> e
|
||||
// <- e, ee, s, es, psk
|
||||
@(private,rodata)
|
||||
PATTERN_NXpsk2 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .s, .es, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// XNpsk3:
|
||||
// -> e
|
||||
// <- e, ee
|
||||
// -> s, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_XNpsk3 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee},
|
||||
{.s, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// XKpsk3:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es
|
||||
// <- e, ee
|
||||
// -> s, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_XKpsk3 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es},
|
||||
{.e, .ee},
|
||||
{.s, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// XXpsk3:
|
||||
// -> e
|
||||
// <- e, ee, s, es
|
||||
// -> s, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_XXpsk3 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .s, .es},
|
||||
{.s, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// KNpsk0:
|
||||
// -> s
|
||||
// ...
|
||||
// -> psk, e
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_KNpsk0 : Message_Pattern = {
|
||||
pre_messages = {.ini_s},
|
||||
messages = {
|
||||
{.psk, .e},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// KNpsk2:
|
||||
// -> s
|
||||
// ...
|
||||
// -> e
|
||||
// <- e, ee, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_KNpsk2 : Message_Pattern = {
|
||||
pre_messages = {.ini_s},
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// KKpsk0:
|
||||
// -> s
|
||||
// <- s
|
||||
// ...
|
||||
// -> psk, e, es, ss
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_KKpsk0 : Message_Pattern = {
|
||||
pre_messages = {.ini_s, .res_s},
|
||||
messages = {
|
||||
{.psk, .e, .es, .ss},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// KKpsk2:
|
||||
// -> s
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, ss
|
||||
// <- e, ee, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_KKpsk2 : Message_Pattern = {
|
||||
pre_messages = {.ini_s, .res_s},
|
||||
messages = {
|
||||
{.e, .es, .ss},
|
||||
{.e, .ee, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// KXpsk2:
|
||||
// -> s
|
||||
// ...
|
||||
// -> e
|
||||
// <- e, ee, se, s, es, psk
|
||||
@(private,rodata)
|
||||
PATTERN_KXpsk2 : Message_Pattern = {
|
||||
pre_messages = {.ini_s},
|
||||
messages = {
|
||||
{.e},
|
||||
{.e, .ee, .se, .s, .es, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// INpsk1:
|
||||
// -> e, s, psk
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_INpsk1 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e, .s, .psk},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// INpsk2:
|
||||
// -> e, s
|
||||
// <- e, ee, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_INpsk2 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e, .s},
|
||||
{.e, .ee, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// IKpsk1:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, s, ss, psk
|
||||
// <- e, ee, se
|
||||
@(private,rodata)
|
||||
PATTERN_IKpsk1 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es, .s, .ss, .psk},
|
||||
{.e, .ee, .se},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// IKpsk2:
|
||||
// <- s
|
||||
// ...
|
||||
// -> e, es, s, ss
|
||||
// <- e, ee, se, psk
|
||||
@(private,rodata)
|
||||
PATTERN_IKpsk2 : Message_Pattern = {
|
||||
pre_messages = {.res_s},
|
||||
messages = {
|
||||
{.e, .es, .s, .ss},
|
||||
{.e, .ee, .se, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
|
||||
// IXpsk2:
|
||||
// -> e, s
|
||||
// <- e, ee, se, s, es, psk
|
||||
@(private,rodata)
|
||||
PATTERN_IXpsk2 : Message_Pattern = {
|
||||
pre_messages = nil,
|
||||
messages = {
|
||||
{.e, .s},
|
||||
{.e, .ee, .se, .s, .es, .psk},
|
||||
},
|
||||
is_psk = true,
|
||||
}
|
||||
1076
core/crypto/noise/protocol.odin
Normal file
1076
core/crypto/noise/protocol.odin
Normal file
File diff suppressed because it is too large
Load Diff
305
core/crypto/noise/test_crypto_noise.odin
Normal file
305
core/crypto/noise/test_crypto_noise.odin
Normal file
@@ -0,0 +1,305 @@
|
||||
package noise
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import "core:crypto/aead"
|
||||
import "core:crypto/ecdh"
|
||||
import "core:crypto/hash"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:math/rand"
|
||||
import "core:testing"
|
||||
|
||||
@(private = "file")
|
||||
DH_CURVES :: []ecdh.Curve {
|
||||
.X25519,
|
||||
.X448,
|
||||
}
|
||||
@(private = "file")
|
||||
CIPHERS :: []aead.Algorithm{
|
||||
.AES_GCM_256,
|
||||
.CHACHA20POLY1305,
|
||||
}
|
||||
@(private = "file")
|
||||
HASHES :: []hash.Algorithm{
|
||||
.SHA256,
|
||||
.SHA512,
|
||||
.BLAKE2S,
|
||||
.BLAKE2B,
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_supported_protocols :: proc(t: ^testing.T) {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.info("rand_bytes not supported - skipping")
|
||||
return
|
||||
}
|
||||
|
||||
protocol: Test_Protocol
|
||||
for pattern in Handshake_Pattern {
|
||||
if pattern == .Invalid {
|
||||
continue
|
||||
}
|
||||
protocol.handshake_pattern = pattern
|
||||
for dh in DH_CURVES {
|
||||
protocol.dh = dh
|
||||
for cipher in CIPHERS {
|
||||
protocol.cipher = cipher
|
||||
for hash in HASHES {
|
||||
protocol.hash = hash
|
||||
if !testing.expectf(
|
||||
t,
|
||||
test_noise_one_protocol(t, &protocol, context.temp_allocator),
|
||||
"Failed protocol: %v", protocol,
|
||||
) {
|
||||
testing.fail(t)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
test_noise_one_protocol :: proc(t: ^testing.T, protocol: ^Test_Protocol, allocator := context.allocator) -> bool {
|
||||
protocol_name := test_protocol_string(protocol, allocator)
|
||||
defer delete(protocol_name, allocator)
|
||||
|
||||
log.debugf("crypto/noise: %s", protocol_name)
|
||||
|
||||
is_one_way := pattern_is_one_way(protocol.handshake_pattern)
|
||||
|
||||
initiator_s, responder_s: ecdh.Private_Key
|
||||
ini_s, res_s: ^ecdh.Private_Key
|
||||
ini_s_pub, res_s_pub: ^ecdh.Public_Key
|
||||
|
||||
pre, hs := pattern_requires_initiator_s(protocol.handshake_pattern)
|
||||
if pre || hs {
|
||||
if !testing.expect(t, ecdh.private_key_generate(&initiator_s, protocol.dh), "failed to generate initiator s") {
|
||||
return false
|
||||
}
|
||||
ini_s = &initiator_s
|
||||
if pre {
|
||||
ini_s_pub = &initiator_s._pub_key
|
||||
}
|
||||
}
|
||||
pre, hs = pattern_requires_responder_s(protocol.handshake_pattern)
|
||||
if pre || hs {
|
||||
if !testing.expect(t, ecdh.private_key_generate(&responder_s, protocol.dh), "failed to generate responder s") {
|
||||
return false
|
||||
}
|
||||
res_s = &responder_s
|
||||
if pre {
|
||||
res_s_pub = &responder_s._pub_key
|
||||
}
|
||||
}
|
||||
|
||||
psk_buf: [32]byte = ---
|
||||
psk: []byte
|
||||
if pattern_is_psk(protocol.handshake_pattern) {
|
||||
crypto.rand_bytes(psk_buf[:])
|
||||
psk = psk_buf[:]
|
||||
}
|
||||
|
||||
ini_hs, res_hs: Handshake_State
|
||||
status := handshake_init(&ini_hs, true, nil, ini_s, res_s_pub, protocol_name, psk)
|
||||
if !testing.expectf(t, status == .Ok, "failed to initialize initiator Handshake_State: %v", status) {
|
||||
return false
|
||||
}
|
||||
status = handshake_init(&res_hs, false, nil, res_s, ini_s_pub, protocol_name, psk)
|
||||
if !testing.expectf(t, status == .Ok, "failed to initialize responder Handshake_State: %v", status) {
|
||||
return false
|
||||
}
|
||||
|
||||
ini_status, res_status: Status
|
||||
ini_msg, res_msg: []byte
|
||||
ini_payload, res_payload: []byte
|
||||
hs_msg_buf: [MAX_STEP_MSG_SIZE]byte
|
||||
for i := 0; ; i += 1{
|
||||
if ini_status == .Handshake_Complete && res_status == .Handshake_Complete {
|
||||
break
|
||||
}
|
||||
|
||||
// Test the allocation path
|
||||
res_msg, res_payload, ini_status = handshake_initiator_step(&ini_hs, ini_msg, allocator = allocator)
|
||||
ini_msg = nil
|
||||
|
||||
if ini_status == .Handshake_Complete && res_status == .Handshake_Complete {
|
||||
break
|
||||
}
|
||||
|
||||
if !testing.expectf(t, len(res_payload) == 0, "step %d: unexpected responder payload: %x", i, res_payload) {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, ini_status == .Handshake_Pending || ini_status == .Handshake_Complete, "step %d: initiator step failed: %v", i, ini_status) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test the non-allocation path
|
||||
ini_msg, ini_payload, res_status = handshake_responder_step(&res_hs, res_msg, nil, hs_msg_buf[:])
|
||||
delete(res_msg, allocator)
|
||||
res_msg = nil
|
||||
|
||||
if !testing.expectf(t, len(ini_payload) == 0, "step %d: unexpected initiator payload: %x", i, ini_payload) {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, res_status == .Handshake_Pending || res_status == .Handshake_Complete, "step %d: responder step failed: %v", i, res_status) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
delete(res_msg, allocator)
|
||||
|
||||
hs_pub: ^ecdh.Public_Key
|
||||
if ini_s != nil {
|
||||
hs_pub, status = handshake_peer_identity(&res_hs)
|
||||
if !testing.expect(t, status == .Ok) {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, ecdh.public_key_equal(&ini_s._pub_key, hs_pub), "responder has incorrect initiator identity") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if res_s != nil {
|
||||
hs_pub, status = handshake_peer_identity(&ini_hs)
|
||||
if !testing.expect(t, status == .Ok) {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, ecdh.public_key_equal(&res_s._pub_key, hs_pub), "initiator has incorrect responder identity") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2: []byte
|
||||
h1, status = handshake_hash(&ini_hs)
|
||||
if !testing.expect(t, status == .Ok) {
|
||||
return false
|
||||
}
|
||||
h2, status = handshake_hash(&res_hs)
|
||||
if !testing.expect(t, status == .Ok) {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, bytes.equal(h1, h2), "handshake hash mismatch: %x != %x", h1, h2) {
|
||||
return false
|
||||
}
|
||||
|
||||
ini_cs, res_cs: Cipher_States
|
||||
if !testing.expectf(t, .Ok == handshake_split(&ini_hs, &ini_cs), "failed to split initiator: %v") {
|
||||
return false
|
||||
}
|
||||
if !testing.expectf(t, .Ok == handshake_split(&res_hs, &res_cs), "failed to split responder: %v") {
|
||||
return false
|
||||
}
|
||||
|
||||
handshake_reset(&ini_hs)
|
||||
handshake_reset(&res_hs)
|
||||
|
||||
if !testing.expect(t, test_messages(t, &ini_cs, &res_cs, is_one_way, allocator), "message tests failed") {
|
||||
return false
|
||||
}
|
||||
|
||||
cipherstates_reset(&ini_cs)
|
||||
cipherstates_reset(&res_cs)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
test_messages :: proc(t: ^testing.T, ini_cs, res_cs: ^Cipher_States, is_one_way: bool, allocator := context.allocator) -> bool {
|
||||
ad_buf: [256]byte = ---
|
||||
payload_buf: [MAX_PACKET_SIZE-TAG_SIZE]byte = ---
|
||||
|
||||
for i in 0..<10 {
|
||||
ad := ad_buf[:rand.int_max(len(ad_buf))]
|
||||
payload := payload_buf[:rand.int_max(len(payload_buf))]
|
||||
|
||||
_ = rand.read(payload)
|
||||
_ = rand.read(ad)
|
||||
|
||||
// Initiator -> Responder (allocate buffers)
|
||||
tx_msg, status := seal_message(ini_cs, ad, payload, allocator = allocator)
|
||||
defer delete(tx_msg, allocator)
|
||||
if !testing.expectf(t, status == .Ok, "i->r %d: seal failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
|
||||
rx_dst: []byte
|
||||
rx_dst, status = open_message(res_cs, ad, tx_msg, allocator = allocator)
|
||||
defer delete(rx_dst, allocator)
|
||||
if !testing.expectf(t, status == .Ok, "i->r %d: open failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !testing.expectf(t, bytes.equal(rx_dst, payload), "i->r %d: payload mismatch") {
|
||||
return false
|
||||
}
|
||||
|
||||
if i == 5 {
|
||||
status = cipherstates_rekey(ini_cs, true)
|
||||
if !testing.expectf(t, status == .Ok, "i %d: rekey failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
status = cipherstates_rekey(res_cs, false)
|
||||
if !testing.expectf(t, status == .Ok, "r %d: rekey failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if is_one_way {
|
||||
continue
|
||||
}
|
||||
|
||||
// Responder -> Initiator (reuse allocated buffers)
|
||||
tx_msg, status = seal_message(res_cs, ad, payload, tx_msg)
|
||||
if !testing.expectf(t, status == .Ok, "r->i %d: seal failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
|
||||
_, status = open_message(ini_cs, ad, tx_msg, rx_dst)
|
||||
if !testing.expectf(t, status == .Ok, "r->i %d: open failed: %v", i, status) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !testing.expectf(t, bytes.equal(rx_dst, payload), "r-i %d: payload mismatch") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
Test_Protocol :: struct {
|
||||
handshake_pattern: Handshake_Pattern,
|
||||
dh: ecdh.Curve,
|
||||
cipher: aead.Algorithm,
|
||||
hash: hash.Algorithm,
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
test_protocol_string :: proc(protocol: ^Test_Protocol, allocator := context.allocator) -> string {
|
||||
dh: string
|
||||
#partial switch protocol.dh {
|
||||
case .X25519: dh = "25519"
|
||||
case .X448: dh = "448"
|
||||
case: panic("crypto/noise: unsupported DH")
|
||||
}
|
||||
|
||||
cipher: string
|
||||
#partial switch protocol.cipher {
|
||||
case .AES_GCM_256: cipher = "AESGCM"
|
||||
case .CHACHA20POLY1305: cipher = "ChaChaPoly"
|
||||
case: panic("crypto/noise: unsupported cipher")
|
||||
}
|
||||
|
||||
hash: string
|
||||
#partial switch protocol.hash {
|
||||
case .SHA256: hash = "SHA256"
|
||||
case .SHA512: hash = "SHA512"
|
||||
case .BLAKE2S: hash = "Blake2s"
|
||||
case .BLAKE2B: hash = "Blake2b"
|
||||
case: panic("crypto/noise: unsupported hash")
|
||||
}
|
||||
|
||||
return fmt.aprintf("Noise_%v_%v_%v_%v", protocol.handshake_pattern, dh, cipher, hash, allocator = allocator)
|
||||
}
|
||||
Reference in New Issue
Block a user