Class: Noise::Connection::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/noise/connection/base.rb

Direct Known Subclasses

Initiator, Responder

Constant Summary collapse

MAX_MESSAGE_LENGTH =

The Noise spec caps a handshake or transport message at 65535 bytes. A transport message is the ciphertext, so the plaintext a caller may hand to encrypt is shorter by the authentication tag that ENCRYPT() appends.

65_535
MAX_PLAINTEXT_LENGTH =
MAX_MESSAGE_LENGTH - Noise::State::CipherState::TAG_LENGTH

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil }) ⇒ Base

Returns a new instance of Base.



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/noise/connection/base.rb', line 16

def initialize(name, keypairs: { s: nil, e: nil, rs: nil, re: nil })
  @protocol = Protocol.create(name)

  # parameter keypairs[:e] and keypairs[:s] are strings, so should convert Noise::Key object.
  @local_keypairs = {}
  @local_keypairs[:e] = @protocol.dh_fn.class.from_private(keypairs[:e]) if keypairs[:e]
  @local_keypairs[:s] = @protocol.dh_fn.class.from_private(keypairs[:s]) if keypairs[:s]
  @remote_keys = { rs: keypairs[:rs], re: keypairs[:re] }
  @handshake_started = false
  @handshake_finished = false
  initialize_next_message
end

Instance Attribute Details

#cipher_state_decryptObject (readonly)

Returns the value of attribute cipher_state_decrypt.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def cipher_state_decrypt
  @cipher_state_decrypt
end

#cipher_state_encryptObject (readonly)

Returns the value of attribute cipher_state_encrypt.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def cipher_state_encrypt
  @cipher_state_encrypt
end

#cipher_state_handshakeObject (readonly)

Returns the value of attribute cipher_state_handshake.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def cipher_state_handshake
  @cipher_state_handshake
end

#handshake_finishedObject (readonly)

Returns the value of attribute handshake_finished.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def handshake_finished
  @handshake_finished
end

#handshake_hashObject (readonly)

Returns the value of attribute handshake_hash.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def handshake_hash
  @handshake_hash
end

#handshake_startedObject (readonly)

Returns the value of attribute handshake_started.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def handshake_started
  @handshake_started
end

#handshake_stateObject (readonly)

Returns the value of attribute handshake_state.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def handshake_state
  @handshake_state
end

#prologueObject

Returns the value of attribute prologue.



14
15
16
# File 'lib/noise/connection/base.rb', line 14

def prologue
  @prologue
end

#protocolObject (readonly)

Returns the value of attribute protocol.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def protocol
  @protocol
end

#psksObject

Returns the value of attribute psks.



14
15
16
# File 'lib/noise/connection/base.rb', line 14

def psks
  @psks
end

#rsObject (readonly)

Returns the value of attribute rs.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def rs
  @rs
end

#sObject (readonly)

Returns the value of attribute s.



12
13
14
# File 'lib/noise/connection/base.rb', line 12

def s
  @s
end

Instance Method Details

#decrypt(data) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/noise/connection/base.rb', line 101

def decrypt(data)
  cipher_state = transport_cipher_state(:decrypt)
  # Rejected before the cipher state is used, so an over-long message leaves n untouched
  # and the connection usable, exactly as a failed decryption does.
  if data.bytesize > MAX_MESSAGE_LENGTH
    raise Noise::Exceptions::MessageTooLongError,
          "Message is #{data.bytesize} bytes, which exceeds the maximum of #{MAX_MESSAGE_LENGTH}."
  end

  cipher_state.decrypt_with_ad('', data)
end

#decryption_nonceInteger

Returns the nonce the next #decrypt call uses.

Returns:

  • (Integer)

    the nonce the next #decrypt call uses.



119
120
121
# File 'lib/noise/connection/base.rb', line 119

def decryption_nonce
  transport_cipher_state(:decrypt).n
end

#decryption_nonce=(nonce) ⇒ Object

Sets the nonce of the next #decrypt call. This is how the Noise spec handles transport messages that arrive out of order: the receiver sets n to the nonce of the message it is about to decrypt, and restores the previous value if the message fails to authenticate.

Parameters:

  • nonce (Integer)

    a value between 0 and CipherState::MAX_NONCE.



136
137
138
# File 'lib/noise/connection/base.rb', line 136

def decryption_nonce=(nonce)
  transport_cipher_state(:decrypt).nonce = nonce
end

#encrypt(data) ⇒ Object



91
92
93
94
95
96
97
98
99
# File 'lib/noise/connection/base.rb', line 91

def encrypt(data)
  cipher_state = transport_cipher_state(:encrypt)
  if data.bytesize > MAX_PLAINTEXT_LENGTH
    raise Noise::Exceptions::MessageTooLongError,
          "Plaintext is #{data.bytesize} bytes, which exceeds the maximum of #{MAX_PLAINTEXT_LENGTH}."
  end

  cipher_state.encrypt_with_ad('', data)
end

#encryption_nonceInteger

Returns the nonce the next #encrypt call uses.

Returns:

  • (Integer)

    the nonce the next #encrypt call uses.



114
115
116
# File 'lib/noise/connection/base.rb', line 114

def encryption_nonce
  transport_cipher_state(:encrypt).n
end

#encryption_nonce=(nonce) ⇒ Object

Sets the nonce of the next #encrypt call. Needed when the transport layer numbers the messages itself instead of relying on the sender and the receiver counting in step.

Parameters:

  • nonce (Integer)

    a value between 0 and CipherState::MAX_NONCE.



127
128
129
# File 'lib/noise/connection/base.rb', line 127

def encryption_nonce=(nonce)
  transport_cipher_state(:encrypt).nonce = nonce
end

#fallback(fallback_name) ⇒ Object

Restarts the handshake with a fallback pattern, carrying over the keys of the aborted one.

The roles swap here: the party that wrote the aborted message now reads, and the one that failed to read it now writes. Both sides are already in that state, so @next_message is deliberately left as it is rather than reset through initialize_next_message.



40
41
42
43
44
45
46
47
# File 'lib/noise/connection/base.rb', line 40

def fallback(fallback_name)
  @protocol = Protocol.create(fallback_name)
  @handshake_started = false
  @handshake_finished = false
  @local_keypairs = { e: @handshake_state.e, s: @handshake_state.s }
  @remote_keys = { re: @handshake_state.re, rs: @handshake_state.rs }
  start_handshake
end

#handshake_done(_c1, _c2) ⇒ Object



183
184
185
186
187
188
189
190
# File 'lib/noise/connection/base.rb', line 183

def handshake_done(_c1, _c2)
  @handshake_hash = @symmetric_state.handshake_hash
  @s = @handshake_state.s
  @rs = @handshake_state.rs
  @handshake_state = nil
  @symmetric_state = nil
  @cipher_state_handshake = nil
end

#initialise_handshake_stateObject



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/noise/connection/base.rb', line 49

def initialise_handshake_state
  @handshake_state = Noise::State::HandshakeState.new(
    self,
    initiator?,
    @prologue,
    @local_keypairs,
    @remote_keys
  )
  @symmetric_state = @handshake_state.symmetric_state
  @cipher_state_handshake = @symmetric_state.cipher_state
end

#missing_keypairs?Boolean

Returns:

  • (Boolean)


170
171
172
173
# File 'lib/noise/connection/base.rb', line 170

def missing_keypairs?
  keypairs = @local_keypairs.merge(@remote_keys)
  @protocol.pattern.required_keypairs(initiator?).any? { |keypair| !keypairs[keypair] }
end

#read_message(data) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/noise/connection/base.rb', line 77

def read_message(data)
  # Call NoiseConnection.start_handshake first
  raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
  raise Noise::Exceptions::NoiseHandshakeError if @next_message != :read
  raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished
  raise Noise::Exceptions::NoiseHandshakeError, 'Message exceeds the maximum length.' if
    data.bytesize > MAX_MESSAGE_LENGTH

  @next_message = :write
  buffer = +''
  @handshake_finished = @handshake_state.read_message(data, buffer)
  buffer
end

#rekey_decryptionvoid

This method returns an undefined value.

Replaces the key used by #decrypt with REKEY(k). See #rekey_encryption.



153
154
155
156
# File 'lib/noise/connection/base.rb', line 153

def rekey_decryption
  transport_cipher_state(:decrypt).rekey
  nil
end

#rekey_encryptionvoid

This method returns an undefined value.

Replaces the key used by #encrypt with REKEY(k), so that the old key cannot decrypt the messages that follow. Both parties must rekey the matching direction at the same point of the message stream, which is up to the application protocol to agree on.



145
146
147
148
# File 'lib/noise/connection/base.rb', line 145

def rekey_encryption
  transport_cipher_state(:encrypt).rekey
  nil
end

#start_handshakeObject



29
30
31
32
33
# File 'lib/noise/connection/base.rb', line 29

def start_handshake
  validate
  initialise_handshake_state
  @handshake_started = true
end

#validateObject



175
176
177
178
179
180
181
# File 'lib/noise/connection/base.rb', line 175

def validate
  validate_psk! if @protocol.psk?

  raise Noise::Exceptions::NoiseValidationError if missing_keypairs?

  true
end

#validate_psk!Object



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/noise/connection/base.rb', line 158

def validate_psk!
  raise Noise::Exceptions::NoisePSKError, 'psks are not set.' if @psks.nil?
  # Invalid psk length! Has to be 32 bytes long
  raise Noise::Exceptions::NoisePSKError, 'psks have to be 32 bytes long.' if
    @psks.any? { |psk| psk.bytesize != 32 }

  return if @protocol.pattern.psk_count == @psks.count

  raise Noise::Exceptions::NoisePSKError,
        "This protocol needs #{@protocol.pattern.psk_count} psks, got #{@psks.count}."
end

#write_message(payload = '') ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/noise/connection/base.rb', line 61

def write_message(payload = '')
  # Call NoiseConnection.start_handshake first
  raise Noise::Exceptions::NoiseHandshakeError unless @handshake_started
  raise Noise::Exceptions::NoiseHandshakeError if @next_message != :write
  raise Noise::Exceptions::NoiseHandshakeError if @handshake_finished

  if @handshake_state.expected_message_length(payload.bytesize) > MAX_MESSAGE_LENGTH
    raise Noise::Exceptions::NoiseHandshakeError, 'Message exceeds the maximum length.'
  end

  @next_message = :read
  buffer = +''
  @handshake_finished = @handshake_state.write_message(payload, buffer)
  buffer
end