Class: Noise::State::HandshakeState

Inherits:
Object
  • Object
show all
Defined in:
lib/noise/state/handshake_state.rb

Overview

A HandshakeState object contains a SymmetricState plus the following variables, any of which may be empty. Empty is a special value which indicates the variable has not yet been initialized.

s: The local static key pair e: The local ephemeral key pair rs: The remote party's static public key re: The remote party's ephemeral public key

A HandshakeState also has variables to track its role, and the remaining portion of the handshake pattern:

initiator: A boolean indicating the initiator or responder role.

message_patterns: A sequence of message patterns. Each message pattern is a sequence of tokens from the set ("e", "s", "ee", "es", "se", "ss").

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, initiator, prologue, local_keypairs, remote_keys) ⇒ HandshakeState

Returns a new instance of HandshakeState.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/noise/state/handshake_state.rb', line 22

def initialize(connection, initiator, prologue, local_keypairs, remote_keys)
  @connection = connection
  @protocol = connection.protocol
  @symmetric_state = SymmetricState.initialize_symmetric(@protocol, connection, prologue: prologue)
  @initiator = initiator
  @s = local_keypairs[:s]
  @e = local_keypairs[:e]
  @rs = remote_keys[:rs]
  @re = remote_keys[:re]

  initiator_keypair_getter, responder_keypair_getter = get_keypair_getter(initiator)

  # Sets message_patterns to the message patterns from handshake_pattern. The inner arrays are
  # copied too, so consuming them here can never reach back into the shared Pattern.
  @message_patterns = @protocol.pattern.tokens.map(&:dup)

  process_initiator_pre_messages(initiator_keypair_getter)
  process_fallback(initiator_keypair_getter)
  process_responder_pre_messages(responder_keypair_getter)
end

Instance Attribute Details

#eObject (readonly)

Returns the value of attribute e.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def e
  @e
end

#message_patternsObject (readonly)

Returns the value of attribute message_patterns.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def message_patterns
  @message_patterns
end

#reObject (readonly)

Returns the value of attribute re.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def re
  @re
end

#rsObject (readonly)

Returns the value of attribute rs.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def rs
  @rs
end

#sObject (readonly)

Returns the value of attribute s.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def s
  @s
end

#symmetric_stateObject (readonly)

Returns the value of attribute symmetric_state.



20
21
22
# File 'lib/noise/state/handshake_state.rb', line 20

def symmetric_state
  @symmetric_state
end

Instance Method Details

#expected_message_length(payload_size) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/noise/state/handshake_state.rb', line 81

def expected_message_length(payload_size)
  has_key = @symmetric_state.cipher_state.key?
  pattern = @message_patterns.first
  len = pattern.inject(0) do |l, token|
    case token
    when Noise::Token::E
      l += @protocol.dh_fn.dhlen
      has_key = true if @protocol.psk?
    when Noise::Token::S
      l += @protocol.dh_fn.dhlen
      l += 16 if has_key
    else
      has_key = true
    end
    l
  end
  len += payload_size
  len += 16 if has_key
  len
end

#get_keypair_getter(initiator) ⇒ Object



43
44
45
46
47
48
49
# File 'lib/noise/state/handshake_state.rb', line 43

def get_keypair_getter(initiator)
  if initiator
    [local_keypair_getter, remote_keypair_getter]
  else
    [remote_keypair_getter, local_keypair_getter]
  end
end

#local_keypair_getterObject



51
52
53
# File 'lib/noise/state/handshake_state.rb', line 51

def local_keypair_getter
  ->(token) { instance_variable_get("@#{token}").public_key }
end

#process_fallback(initiator_keypair_getter) ⇒ Object



66
67
68
69
70
71
72
# File 'lib/noise/state/handshake_state.rb', line 66

def process_fallback(initiator_keypair_getter)
  return unless @protocol.pattern.fallback

  message = @message_patterns.delete_at(0).first
  public_key = initiator_keypair_getter.call(message)
  @symmetric_state.mix_hash(public_key)
end

#process_initiator_pre_messages(keypair_getter) ⇒ Object



59
60
61
62
63
64
# File 'lib/noise/state/handshake_state.rb', line 59

def process_initiator_pre_messages(keypair_getter)
  @protocol.pattern.initiator_pre_messages&.map do |token|
    keypair = keypair_getter.call(token)
    @symmetric_state.mix_hash(keypair)
  end
end

#process_responder_pre_messages(keypair_getter) ⇒ Object



74
75
76
77
78
79
# File 'lib/noise/state/handshake_state.rb', line 74

def process_responder_pre_messages(keypair_getter)
  @protocol.pattern.responder_pre_messages&.map do |token|
    keypair = keypair_getter.call(token)
    @symmetric_state.mix_hash(keypair)
  end
end

#read_message(message, payload_buffer) ⇒ Boolean

Takes a byte sequence containing a Noise handshake message, and a payload_buffer to write the message's plaintext payload into

Returns:

  • (Boolean)

    true if this was the last handshake message, false otherwise.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/noise/state/handshake_state.rb', line 134

def read_message(message, payload_buffer)
  pattern = @message_patterns.shift
  pattern.each do |token|
    case token
    when Noise::Token::E
      message, @re = extract_key(message, false)
      mix_e(@re)
    when Noise::Token::S
      message, @rs = extract_key(message, true)
    when Noise::Token::EE, Noise::Token::ES, Noise::Token::SE, Noise::Token::SS
      token.mix(@symmetric_state, @protocol.dh_fn, @initiator, self)
    when Noise::Token::PSK
      mix_psk
    end
  end
  payload_buffer << @symmetric_state.decrypt_and_hash(message)
  finish_handshake
end

#remote_keypair_getterObject



55
56
57
# File 'lib/noise/state/handshake_state.rb', line 55

def remote_keypair_getter
  ->(token) { instance_variable_get("@r#{token}") }
end

#write_message(payload, message_buffer) ⇒ Boolean

Takes a payload byte sequence which may be zero-length, and a message_buffer to write the output into

Returns:

  • (Boolean)

    true if this was the last handshake message, false otherwise.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/noise/state/handshake_state.rb', line 104

def write_message(payload, message_buffer)
  pattern = @message_patterns.shift

  pattern.each do |token|
    case token
    when Noise::Token::E
      # A fresh keypair per handshake is what the pattern's forward secrecy rests on. Two
      # things preset @e. Connection::Base#fallback carries the ephemeral of the aborted
      # handshake into the new state, which the fallback patterns require: process_fallback
      # mixes its public key as a pre-message. A caller passing keypairs[:e] to
      # Connection::Base#initialize also presets it, and that is supported for reproducing
      # the official test vectors and nothing else.
      @e ||= @protocol.dh_fn.generate_keypair
      message_buffer << @e.public_key
      mix_e(@e.public_key)
    when Noise::Token::S
      message_buffer << @symmetric_state.encrypt_and_hash(@s.public_key)
    when Noise::Token::EE, Noise::Token::ES, Noise::Token::SE, Noise::Token::SS
      token.mix(@symmetric_state, @protocol.dh_fn, @initiator, self)
    when Noise::Token::PSK
      mix_psk
    end
  end
  message_buffer << @symmetric_state.encrypt_and_hash(payload)
  finish_handshake
end