tttls1.3 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,18 +13,16 @@ module TTTLS13
13
13
  def initialize(socket)
14
14
  @socket = socket
15
15
  @endpoint = nil # Symbol or String, :client or :server
16
- @key_schedule = nil # TTTLS13::KeySchedule
17
- @priv_keys = {} # Hash of NamedGroup => OpenSSL::PKey::$Object
18
- @read_cipher = Cryptograph::Passer.new
19
- @write_cipher = Cryptograph::Passer.new
20
- @transcript = Transcript.new
16
+ @ap_wcipher = Cryptograph::Passer.new
17
+ @ap_rcipher = Cryptograph::Passer.new
18
+ @alert_wcipher = Cryptograph::Passer.new
21
19
  @message_queue = [] # Array of TTTLS13::Message::$Object
22
20
  @binary_buffer = '' # deposit Record.surplus_binary
23
21
  @cipher_suite = nil # TTTLS13::CipherSuite
24
- @notyet_application_secret = true
22
+ @named_group = nil # TTTLS13::NamedGroup
23
+ @signature_scheme = nil # TTTLS13::SignatureScheme
25
24
  @state = 0 # ClientState or ServerState
26
25
  @send_record_size = Message::DEFAULT_RECORD_SIZE_LIMIT
27
- @psk = nil # String
28
26
  end
29
27
 
30
28
  # @raise [TTTLS13::Error::ConfigError]
@@ -41,7 +39,7 @@ module TTTLS13
41
39
 
42
40
  message = nil
43
41
  loop do
44
- message = recv_message
42
+ message = recv_message(receivable_ccs: false, cipher: @ap_rcipher)
45
43
  # At any time after the server has received the client Finished
46
44
  # message, it MAY send a NewSessionTicket message.
47
45
  break unless message.is_a?(Message::NewSessionTicket)
@@ -70,16 +68,33 @@ module TTTLS13
70
68
  (@endpoint == :server && @state == ServerState::CONNECTED)
71
69
 
72
70
  ap = Message::ApplicationData.new(binary)
73
- send_application_data(ap, @write_cipher)
71
+ send_application_data(ap, @ap_wcipher)
74
72
  end
75
73
 
76
74
  def close
75
+ return if @state == EOF
76
+
77
77
  send_alert(:close_notify)
78
78
  @state = EOF
79
79
 
80
80
  nil
81
81
  end
82
82
 
83
+ # @return [TTTLS13::CipherSuite, nil]
84
+ def negotiated_cipher_suite
85
+ @cipher_suite
86
+ end
87
+
88
+ # @return [TTTLS13::NamedGroup, nil]
89
+ def negotiated_named_group
90
+ @named_group
91
+ end
92
+
93
+ # @return [TTTLS13::SignatureScheme, nil]
94
+ def negotiated_signature_scheme
95
+ @signature_scheme
96
+ end
97
+
83
98
  private
84
99
 
85
100
  # @param cipher_suite [TTTLS13::CipherSuite]
@@ -99,12 +114,12 @@ module TTTLS13
99
114
 
100
115
  # @param type [TTTLS13::Message::ContentType]
101
116
  # @param messages [Array of TTTLS13::Message::$Object] handshake messages
102
- # @param write_cipher [TTTLS13::Cryptograph::Aead]
103
- def send_handshakes(type, messages, write_cipher)
117
+ # @param cipher [TTTLS13::Cryptograph::Aead, Passer]
118
+ def send_handshakes(type, messages, cipher)
104
119
  record = Message::Record.new(
105
120
  type: type,
106
121
  messages: messages,
107
- cipher: write_cipher
122
+ cipher: cipher
108
123
  )
109
124
  send_record(record)
110
125
  end
@@ -120,13 +135,13 @@ module TTTLS13
120
135
  end
121
136
 
122
137
  # @param message [TTTLS13::Message::ApplicationData]
123
- # @param write_cipher [TTTLS13::Cryptograph::Aead]
124
- def send_application_data(message, write_cipher)
138
+ # @param cipher [TTTLS13::Cryptograph::Aead]
139
+ def send_application_data(message, cipher)
125
140
  ap_record = Message::Record.new(
126
141
  type: Message::ContentType::APPLICATION_DATA,
127
142
  legacy_record_version: Message::ProtocolVersion::TLS_1_2,
128
143
  messages: [message],
129
- cipher: write_cipher
144
+ cipher: cipher
130
145
  )
131
146
  send_record(ap_record)
132
147
  end
@@ -138,12 +153,12 @@ module TTTLS13
138
153
  )
139
154
  type = Message::ContentType::ALERT
140
155
  type = Message::ContentType::APPLICATION_DATA \
141
- if @write_cipher.is_a?(Cryptograph::Aead)
156
+ if @alert_wcipher.is_a?(Cryptograph::Aead)
142
157
  alert_record = Message::Record.new(
143
158
  type: type,
144
159
  legacy_record_version: Message::ProtocolVersion::TLS_1_2,
145
160
  messages: [message],
146
- cipher: @write_cipher
161
+ cipher: @alert_wcipher
147
162
  )
148
163
  send_record(alert_record)
149
164
  end
@@ -154,23 +169,26 @@ module TTTLS13
154
169
  @socket.write(record.serialize(@send_record_size))
155
170
  end
156
171
 
172
+ # @param receivable_ccs [Boolean]
173
+ # @param cipher [TTTLS13::Cryptograph::Aead, Passer]
174
+ #
157
175
  # @raise [TTTLS13::Error::ErrorAlerts
158
176
  #
159
177
  # @return [TTTLS13::Message::$Object]
160
178
  # rubocop: disable Metrics/CyclomaticComplexity
161
- def recv_message
179
+ def recv_message(receivable_ccs:, cipher:)
162
180
  return @message_queue.shift unless @message_queue.empty?
163
181
 
164
182
  messages = nil
165
183
  loop do
166
- record = recv_record
184
+ record = recv_record(cipher)
167
185
  case record.type
168
186
  when Message::ContentType::HANDSHAKE,
169
187
  Message::ContentType::APPLICATION_DATA
170
188
  messages = record.messages
171
189
  break unless messages.empty?
172
190
  when Message::ContentType::CCS
173
- terminate(:unexpected_message) unless receivable_ccs?
191
+ terminate(:unexpected_message) unless receivable_ccs
174
192
  next
175
193
  when Message::ContentType::ALERT
176
194
  handle_received_alert(record.messages.first)
@@ -191,15 +209,17 @@ module TTTLS13
191
209
  end
192
210
  # rubocop: enable Metrics/CyclomaticComplexity
193
211
 
212
+ # @param wcipher [TTTLS13::Cryptograph::Aead, Passer]
213
+ #
194
214
  # @return [TTTLS13::Message::Record]
195
- def recv_record
215
+ def recv_record(cipher)
196
216
  binary = @socket.read(5)
197
217
  record_len = Convert.bin2i(binary.slice(3, 2))
198
218
  binary += @socket.read(record_len)
199
219
 
200
220
  begin
201
221
  buffer = @binary_buffer
202
- record = Message::Record.deserialize(binary, @read_cipher, buffer)
222
+ record = Message::Record.deserialize(binary, cipher, buffer)
203
223
  @binary_buffer = record.surplus_binary
204
224
  rescue Error::ErrorAlerts => e
205
225
  terminate(e.message.to_sym)
@@ -215,32 +235,37 @@ module TTTLS13
215
235
  record
216
236
  end
217
237
 
238
+ # @param ch1 [TTTLS13::Message::ClientHello]
239
+ # @param hrr [TTTLS13::Message::ServerHello]
240
+ # @param ch [TTTLS13::Message::ClientHello]
241
+ # @param binder_key [String]
218
242
  # @param digest [String] name of digest algorithm
219
- # @param transcript [TTTLS13::Transcript]
220
243
  #
221
244
  # @return [String]
222
- def do_sign_psk_binder(digest, transcript)
245
+ def do_sign_psk_binder(ch1:, hrr:, ch:, binder_key:, digest:)
223
246
  # TODO: ext binder
224
- secret = @key_schedule.binder_key_res
225
247
  hash_len = OpenSSL::Digest.new(digest).digest_length
248
+ tt = Transcript.new
249
+ tt.merge!(
250
+ CH1 => ch1,
251
+ HRR => hrr,
252
+ CH => ch
253
+ )
226
254
  # transcript-hash (CH1 + HRR +) truncated-CH
227
- hash = transcript.truncate_hash(digest, CH, hash_len + 3)
228
- OpenSSL::HMAC.digest(digest, secret, hash)
255
+ hash = tt.truncate_hash(digest, CH, hash_len + 3)
256
+ OpenSSL::HMAC.digest(digest, binder_key, hash)
229
257
  end
230
258
 
231
- # @param private_key [OpenSSL::PKey::PKey]
259
+ # @param key [OpenSSL::PKey::PKey]
232
260
  # @param signature_scheme [TTTLS13::SignatureScheme]
233
261
  # @param context [String]
234
- # @param handshake_context_end [Integer]
262
+ # @param hash [String]
235
263
  #
236
264
  # @raise [TTTLS13::Error::ErrorAlerts]
237
265
  #
238
266
  # @return [String]
239
267
  # rubocop: disable Metrics/CyclomaticComplexity
240
- def do_sign_certificate_verify(private_key:, signature_scheme:, context:,
241
- handshake_context_end:)
242
- digest = CipherSuite.digest(@cipher_suite)
243
- hash = @transcript.hash(digest, handshake_context_end)
268
+ def do_sign_certificate_verify(key:, signature_scheme:, context:, hash:)
244
269
  content = "\x20" * 64 + context + "\x00" + hash
245
270
 
246
271
  # RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether
@@ -249,24 +274,24 @@ module TTTLS13
249
274
  when SignatureScheme::RSA_PKCS1_SHA256,
250
275
  SignatureScheme::RSA_PSS_RSAE_SHA256,
251
276
  SignatureScheme::RSA_PSS_PSS_SHA256
252
- private_key.sign_pss('SHA256', content, salt_length: :digest,
253
- mgf1_hash: 'SHA256')
277
+ key.sign_pss('SHA256', content, salt_length: :digest,
278
+ mgf1_hash: 'SHA256')
254
279
  when SignatureScheme::RSA_PKCS1_SHA384,
255
280
  SignatureScheme::RSA_PSS_RSAE_SHA384,
256
281
  SignatureScheme::RSA_PSS_PSS_SHA384
257
- private_key.sign_pss('SHA384', content, salt_length: :digest,
258
- mgf1_hash: 'SHA384')
282
+ key.sign_pss('SHA384', content, salt_length: :digest,
283
+ mgf1_hash: 'SHA384')
259
284
  when SignatureScheme::RSA_PKCS1_SHA512,
260
285
  SignatureScheme::RSA_PSS_RSAE_SHA512,
261
286
  SignatureScheme::RSA_PSS_PSS_SHA512
262
- private_key.sign_pss('SHA512', content, salt_length: :digest,
263
- mgf1_hash: 'SHA512')
287
+ key.sign_pss('SHA512', content, salt_length: :digest,
288
+ mgf1_hash: 'SHA512')
264
289
  when SignatureScheme::ECDSA_SECP256R1_SHA256
265
- private_key.sign('SHA256', content)
290
+ key.sign('SHA256', content)
266
291
  when SignatureScheme::ECDSA_SECP384R1_SHA384
267
- private_key.sign('SHA384', content)
292
+ key.sign('SHA384', content)
268
293
  when SignatureScheme::ECDSA_SECP521R1_SHA512
269
- private_key.sign('SHA512', content)
294
+ key.sign('SHA512', content)
270
295
  else # TODO: ED25519, ED448
271
296
  terminate(:internal_error)
272
297
  end
@@ -277,17 +302,14 @@ module TTTLS13
277
302
  # @param signature_scheme [TTTLS13::SignatureScheme]
278
303
  # @param signature [String]
279
304
  # @param context [String]
280
- # @param handshake_context_end [Integer]
305
+ # @param hash [String]
281
306
  #
282
307
  # @raise [TTTLS13::Error::ErrorAlerts]
283
308
  #
284
309
  # @return [Boolean]
285
310
  # rubocop: disable Metrics/CyclomaticComplexity
286
311
  def do_verified_certificate_verify?(public_key:, signature_scheme:,
287
- signature:, context:,
288
- handshake_context_end:)
289
- digest = CipherSuite.digest(@cipher_suite)
290
- hash = @transcript.hash(digest, handshake_context_end)
312
+ signature:, context:, hash:)
291
313
  content = "\x20" * 64 + context + "\x00" + hash
292
314
 
293
315
  # RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether
@@ -322,27 +344,22 @@ module TTTLS13
322
344
 
323
345
  # @param digest [String] name of digest algorithm
324
346
  # @param finished_key [String]
325
- # @param handshake_context_end [Integer]
347
+ # @param hash [String]
326
348
  #
327
349
  # @return [String]
328
- def do_sign_finished(digest:, finished_key:, handshake_context_end:)
329
- hash = @transcript.hash(digest, handshake_context_end)
350
+ def sign_finished(digest:, finished_key:, hash:)
330
351
  OpenSSL::HMAC.digest(digest, finished_key, hash)
331
352
  end
332
353
 
354
+ # @param finished [TTTLS13::Message::Finished]
333
355
  # @param digest [String] name of digest algorithm
334
356
  # @param finished_key [String]
335
- # @param handshake_context_end [Integer]
336
- # @param signature [String]
357
+ # @param hash [String]
337
358
  #
338
359
  # @return [Boolean]
339
- def do_verified_finished?(digest:, finished_key:, handshake_context_end:,
340
- signature:)
341
- do_sign_finished(
342
- digest: digest,
343
- finished_key: finished_key,
344
- handshake_context_end: handshake_context_end
345
- ) == signature
360
+ def verified_finished?(finished:, digest:, finished_key:, hash:)
361
+ sign_finished(digest: digest, finished_key: finished_key, hash: hash) \
362
+ == finished.verify_data
346
363
  end
347
364
 
348
365
  # @param key_exchange [String]
@@ -362,8 +379,10 @@ module TTTLS13
362
379
  priv_key.dh_compute_key(pub_key)
363
380
  end
364
381
 
382
+ # @param transcript [TTTLS13::Transcript]
383
+ #
365
384
  # @return [Boolean]
366
- def receivable_ccs?
385
+ def receivable_ccs?(transcript)
367
386
  # Received ccs before the first ClientHello message or after the peer's
368
387
  # Finished message, peer MUST abort.
369
388
  #
@@ -371,8 +390,8 @@ module TTTLS13
371
390
  # between the first and second ClientHello
372
391
  finished = (@endpoint == :client ? SF : CF)
373
392
 
374
- (@transcript.include?(CH) || @transcript.include?(CH1)) &&
375
- !@transcript.include?(finished)
393
+ (transcript.include?(CH) || transcript.include?(CH1)) &&
394
+ !transcript.include?(finished)
376
395
  end
377
396
 
378
397
  # @param symbol [Symbol] key of ALERT_DESCRIPTION
@@ -383,6 +402,9 @@ module TTTLS13
383
402
  raise Error::ErrorAlerts, symbol
384
403
  end
385
404
 
405
+ # @param alert [TTTLS13::Message::Alert]
406
+ #
407
+ # @raise [TTTLS13::Error::ErrorAlerts]
386
408
  def handle_received_alert(alert)
387
409
  unless alert.description == Message::ALERT_DESCRIPTION[:close_notify] ||
388
410
  alert.description == Message::ALERT_DESCRIPTION[:user_canceled]
@@ -390,6 +412,7 @@ module TTTLS13
390
412
  end
391
413
 
392
414
  @state = EOF
415
+ nil
393
416
  end
394
417
 
395
418
  # @param _nst [TTTLS13::Message::NewSessionTicket]
@@ -66,7 +66,7 @@ module TTTLS13
66
66
  end
67
67
 
68
68
  # @return [Boolean]
69
- def only_appearable_extensions?
69
+ def appearable_extensions?
70
70
  cl_exs = @certificate_list.map do |e|
71
71
  e.instance_variable_get(:@extensions).keys
72
72
  end
@@ -127,12 +127,37 @@ module TTTLS13
127
127
  # rubocop: enable Metrics/MethodLength
128
128
 
129
129
  # @return [Boolean]
130
- def only_appearable_extensions?
130
+ def appearable_extensions?
131
131
  exs = @extensions.keys - APPEARABLE_CH_EXTENSIONS
132
132
  return true if exs.empty?
133
133
 
134
134
  !(exs - DEFINED_EXTENSIONS).empty?
135
135
  end
136
+
137
+ # @return [Boolean]
138
+ def negotiated_tls_1_3?
139
+ sv = @extensions[ExtensionType::SUPPORTED_VERSIONS]
140
+
141
+ @legacy_version == ProtocolVersion::TLS_1_2 &&
142
+ (sv&.versions || []).include?(ProtocolVersion::TLS_1_3)
143
+ end
144
+
145
+ # @return [Boolean]
146
+ def valid_key_share?
147
+ ks = @extensions[Message::ExtensionType::KEY_SHARE]
148
+ ks_groups = ks&.key_share_entry&.map(&:group) || []
149
+ sg = @extensions[Message::ExtensionType::SUPPORTED_GROUPS]
150
+ sg_groups = sg&.named_group_list || []
151
+
152
+ # Each KeyShareEntry value MUST correspond to a group offered in the
153
+ # "supported_groups" extension and MUST appear in the same order.
154
+ #
155
+ # Clients MUST NOT offer multiple KeyShareEntry values for the same
156
+ # group.
157
+ (ks_groups - sg_groups).empty? &&
158
+ sg_groups.filter { |g| ks_groups.include?(g) } == ks_groups &&
159
+ ks_groups.uniq == ks_groups
160
+ end
136
161
  end
137
162
  end
138
163
  end
@@ -58,7 +58,7 @@ module TTTLS13
58
58
  end
59
59
 
60
60
  # @return [Boolean]
61
- def only_appearable_extensions?
61
+ def appearable_extensions?
62
62
  exs = @extensions.keys - APPEARABLE_EE_EXTENSIONS
63
63
  return true if exs.empty?
64
64
 
@@ -92,7 +92,7 @@ module TTTLS13
92
92
  # rubocop: enable Metrics/AbcSize
93
93
 
94
94
  # @return [Boolean]
95
- def only_appearable_extensions?
95
+ def appearable_extensions?
96
96
  exs = @extensions.keys - APPEARABLE_NST_EXTENSIONS
97
97
  return true if exs.empty?
98
98
 
@@ -21,6 +21,12 @@ module TTTLS13
21
21
  ].freeze
22
22
  private_constant :APPEARABLE_HRR_EXTENSIONS
23
23
 
24
+ DOWNGRADE_PROTECTION_TLS_1_2 = "\x44\x4F\x57\x4E\x47\x52\x44\x01"
25
+ private_constant :DOWNGRADE_PROTECTION_TLS_1_2
26
+
27
+ DOWNGRADE_PROTECTION_TLS_1_1 = "\x44\x4F\x57\x4E\x47\x52\x44\x00"
28
+ private_constant :DOWNGRADE_PROTECTION_TLS_1_1
29
+
24
30
  # special value of the SHA-256 of "HelloRetryRequest"
25
31
  HRR_RANDOM \
26
32
  = "\xcf\x21\xad\x74\xe5\x9a\x61\x11\xbe\x1d\x8c\x02\x1e\x65\xb8\x91" \
@@ -130,13 +136,27 @@ module TTTLS13
130
136
  end
131
137
 
132
138
  # @return [Boolean]
133
- def only_appearable_extensions?
139
+ def appearable_extensions?
134
140
  exs = @extensions.keys - APPEARABLE_SH_EXTENSIONS
135
141
  exs = @extensions.keys - APPEARABLE_HRR_EXTENSIONS if hrr?
136
142
  return true if exs.empty?
137
143
 
138
144
  !(exs - DEFINED_EXTENSIONS).empty?
139
145
  end
146
+
147
+ # @return [Booelan]
148
+ def negotiated_tls_1_3?
149
+ sv = @extensions[Message::ExtensionType::SUPPORTED_VERSIONS]
150
+
151
+ @legacy_version == Message::ProtocolVersion::TLS_1_2 &&
152
+ (sv&.versions || []).first == Message::ProtocolVersion::TLS_1_3
153
+ end
154
+
155
+ # @return [Boolean]
156
+ def downgraded?
157
+ [DOWNGRADE_PROTECTION_TLS_1_2,
158
+ DOWNGRADE_PROTECTION_TLS_1_1].include?(@random[-8..])
159
+ end
140
160
  end
141
161
  end
142
162
  end