tttls1.3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +8 -0
  6. data/Gemfile +13 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +52 -0
  9. data/Rakefile +133 -0
  10. data/example/helper.rb +17 -0
  11. data/example/https_client.rb +32 -0
  12. data/example/https_client_using_0rtt.rb +64 -0
  13. data/example/https_client_using_hrr.rb +35 -0
  14. data/example/https_client_using_ticket.rb +56 -0
  15. data/lib/tttls1.3/cipher_suites.rb +102 -0
  16. data/lib/tttls1.3/client.rb +745 -0
  17. data/lib/tttls1.3/connection.rb +380 -0
  18. data/lib/tttls1.3/cryptograph/aead.rb +118 -0
  19. data/lib/tttls1.3/cryptograph/passer.rb +22 -0
  20. data/lib/tttls1.3/cryptograph.rb +3 -0
  21. data/lib/tttls1.3/error.rb +22 -0
  22. data/lib/tttls1.3/key_schedule.rb +242 -0
  23. data/lib/tttls1.3/message/alert.rb +86 -0
  24. data/lib/tttls1.3/message/application_data.rb +27 -0
  25. data/lib/tttls1.3/message/certificate.rb +121 -0
  26. data/lib/tttls1.3/message/certificate_verify.rb +59 -0
  27. data/lib/tttls1.3/message/change_cipher_spec.rb +26 -0
  28. data/lib/tttls1.3/message/client_hello.rb +100 -0
  29. data/lib/tttls1.3/message/encrypted_extensions.rb +65 -0
  30. data/lib/tttls1.3/message/end_of_early_data.rb +29 -0
  31. data/lib/tttls1.3/message/extension/alpn.rb +70 -0
  32. data/lib/tttls1.3/message/extension/cookie.rb +47 -0
  33. data/lib/tttls1.3/message/extension/early_data_indication.rb +58 -0
  34. data/lib/tttls1.3/message/extension/key_share.rb +236 -0
  35. data/lib/tttls1.3/message/extension/pre_shared_key.rb +205 -0
  36. data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +54 -0
  37. data/lib/tttls1.3/message/extension/record_size_limit.rb +46 -0
  38. data/lib/tttls1.3/message/extension/server_name.rb +91 -0
  39. data/lib/tttls1.3/message/extension/signature_algorithms.rb +69 -0
  40. data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +25 -0
  41. data/lib/tttls1.3/message/extension/status_request.rb +106 -0
  42. data/lib/tttls1.3/message/extension/supported_groups.rb +145 -0
  43. data/lib/tttls1.3/message/extension/supported_versions.rb +98 -0
  44. data/lib/tttls1.3/message/extension/unknown_extension.rb +38 -0
  45. data/lib/tttls1.3/message/extensions.rb +173 -0
  46. data/lib/tttls1.3/message/finished.rb +44 -0
  47. data/lib/tttls1.3/message/new_session_ticket.rb +89 -0
  48. data/lib/tttls1.3/message/record.rb +232 -0
  49. data/lib/tttls1.3/message/server_hello.rb +116 -0
  50. data/lib/tttls1.3/message.rb +48 -0
  51. data/lib/tttls1.3/sequence_number.rb +31 -0
  52. data/lib/tttls1.3/signature_scheme.rb +31 -0
  53. data/lib/tttls1.3/transcript.rb +69 -0
  54. data/lib/tttls1.3/utils.rb +91 -0
  55. data/lib/tttls1.3/version.rb +5 -0
  56. data/lib/tttls1.3.rb +16 -0
  57. data/spec/aead_spec.rb +95 -0
  58. data/spec/alert_spec.rb +54 -0
  59. data/spec/alpn_spec.rb +55 -0
  60. data/spec/application_data_spec.rb +26 -0
  61. data/spec/certificate_spec.rb +55 -0
  62. data/spec/certificate_verify_spec.rb +51 -0
  63. data/spec/change_cipher_spec_spec.rb +26 -0
  64. data/spec/cipher_suites_spec.rb +39 -0
  65. data/spec/client_hello_spec.rb +83 -0
  66. data/spec/client_spec.rb +319 -0
  67. data/spec/connection_spec.rb +114 -0
  68. data/spec/cookie_spec.rb +98 -0
  69. data/spec/early_data_indication_spec.rb +64 -0
  70. data/spec/encrypted_extensions_spec.rb +94 -0
  71. data/spec/error_spec.rb +18 -0
  72. data/spec/extensions_spec.rb +170 -0
  73. data/spec/finished_spec.rb +55 -0
  74. data/spec/key_schedule_spec.rb +198 -0
  75. data/spec/key_share_spec.rb +199 -0
  76. data/spec/new_session_ticket_spec.rb +80 -0
  77. data/spec/pre_shared_key_spec.rb +167 -0
  78. data/spec/psk_key_exchange_modes_spec.rb +45 -0
  79. data/spec/record_size_limit_spec.rb +61 -0
  80. data/spec/record_spec.rb +105 -0
  81. data/spec/server_hello_spec.rb +101 -0
  82. data/spec/server_name_spec.rb +110 -0
  83. data/spec/signature_algorithms_cert_spec.rb +73 -0
  84. data/spec/signature_algorithms_spec.rb +100 -0
  85. data/spec/spec_helper.rb +872 -0
  86. data/spec/status_request_spec.rb +73 -0
  87. data/spec/supported_groups_spec.rb +79 -0
  88. data/spec/supported_versions_spec.rb +136 -0
  89. data/spec/transcript_spec.rb +69 -0
  90. data/spec/unknown_extension_spec.rb +90 -0
  91. data/spec/utils_spec.rb +215 -0
  92. data/tttls1.3.gemspec +25 -0
  93. metadata +197 -0
@@ -0,0 +1,745 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ module ClientState
7
+ # initial value is 0, eof value is -1
8
+ START = 1
9
+ WAIT_SH = 2
10
+ WAIT_EE = 3
11
+ WAIT_CERT_CR = 4
12
+ WAIT_CERT = 5
13
+ WAIT_CV = 6
14
+ WAIT_FINISHED = 7
15
+ CONNECTED = 8
16
+ end
17
+
18
+ DEFAULT_CH_CIPHER_SUITES = [
19
+ CipherSuite::TLS_AES_256_GCM_SHA384,
20
+ CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
21
+ CipherSuite::TLS_AES_128_GCM_SHA256
22
+ ].freeze
23
+
24
+ DEFAULT_CH_SIGNATURE_ALGORITHMS = [
25
+ SignatureScheme::ECDSA_SECP256R1_SHA256,
26
+ SignatureScheme::ECDSA_SECP384R1_SHA384,
27
+ SignatureScheme::ECDSA_SECP521R1_SHA512,
28
+ SignatureScheme::RSA_PSS_PSS_SHA256,
29
+ SignatureScheme::RSA_PSS_PSS_SHA384,
30
+ SignatureScheme::RSA_PSS_PSS_SHA512,
31
+ SignatureScheme::RSA_PSS_RSAE_SHA256,
32
+ SignatureScheme::RSA_PSS_RSAE_SHA384,
33
+ SignatureScheme::RSA_PSS_RSAE_SHA512,
34
+ SignatureScheme::RSA_PKCS1_SHA256,
35
+ SignatureScheme::RSA_PKCS1_SHA384,
36
+ SignatureScheme::RSA_PKCS1_SHA512
37
+ ].freeze
38
+
39
+ DEFAULT_CH_NAMED_GROUP_LIST = [
40
+ Message::Extension::NamedGroup::SECP256R1,
41
+ Message::Extension::NamedGroup::SECP384R1,
42
+ Message::Extension::NamedGroup::SECP521R1
43
+ ].freeze
44
+
45
+ DEFAULT_CLIENT_SETTINGS = {
46
+ ca_file: nil,
47
+ cipher_suites: DEFAULT_CH_CIPHER_SUITES,
48
+ signature_algorithms: DEFAULT_CH_SIGNATURE_ALGORITHMS,
49
+ signature_algorithms_cert: nil,
50
+ supported_groups: DEFAULT_CH_NAMED_GROUP_LIST,
51
+ key_share_groups: nil,
52
+ process_new_session_ticket: nil,
53
+ ticket: nil,
54
+ resumption_master_secret: nil,
55
+ psk_cipher_suite: nil,
56
+ ticket_nonce: nil,
57
+ ticket_age_add: nil,
58
+ ticket_timestamp: nil
59
+ }.freeze
60
+
61
+ # rubocop: disable Metrics/ClassLength
62
+ class Client < Connection
63
+ # @param socket [Socket]
64
+ # @param hostname [String]
65
+ # @param settings [Hash]
66
+ def initialize(socket, hostname, **settings)
67
+ super(socket)
68
+
69
+ @endpoint = :client
70
+ @hostname = hostname
71
+ @settings = DEFAULT_CLIENT_SETTINGS.merge(settings)
72
+ @early_data = ''
73
+ @early_data_write_cipher = nil # Cryptograph::$Object
74
+ @accepted_early_data = false
75
+ raise Error::ConfigError unless valid_settings?
76
+ return unless use_psk?
77
+
78
+ digest = CipherSuite.digest(@settings[:psk_cipher_suite])
79
+ @psk = gen_psk_from_nst(@settings[:resumption_master_secret],
80
+ @settings[:ticket_nonce], digest)
81
+ @key_schedule = KeySchedule.new(
82
+ psk: @psk,
83
+ shared_secret: nil,
84
+ cipher_suite: @settings[:psk_cipher_suite],
85
+ transcript: @transcript
86
+ )
87
+ end
88
+
89
+ # NOTE:
90
+ # START <----+
91
+ # Send ClientHello | | Recv HelloRetryRequest
92
+ # [K_send = early data] | |
93
+ # v |
94
+ # / WAIT_SH ----+
95
+ # | | Recv ServerHello
96
+ # | | K_recv = handshake
97
+ # Can | V
98
+ # send | WAIT_EE
99
+ # early | | Recv EncryptedExtensions
100
+ # data | +--------+--------+
101
+ # | Using | | Using certificate
102
+ # | PSK | v
103
+ # | | WAIT_CERT_CR
104
+ # | | Recv | | Recv CertificateRequest
105
+ # | | Certificate | v
106
+ # | | | WAIT_CERT
107
+ # | | | | Recv Certificate
108
+ # | | v v
109
+ # | | WAIT_CV
110
+ # | | | Recv CertificateVerify
111
+ # | +> WAIT_FINISHED <+
112
+ # | | Recv Finished
113
+ # \ | [Send EndOfEarlyData]
114
+ # | K_send = handshake
115
+ # | [Send Certificate [+ CertificateVerify]]
116
+ # Can send | Send Finished
117
+ # app data --> | K_send = K_recv = application
118
+ # after here v
119
+ # CONNECTED
120
+ #
121
+ # https://tools.ietf.org/html/rfc8446#appendix-A
122
+ #
123
+ # rubocop: disable Metrics/AbcSize
124
+ # rubocop: disable Metrics/BlockLength
125
+ # rubocop: disable Metrics/CyclomaticComplexity
126
+ # rubocop: disable Metrics/MethodLength
127
+ # rubocop: disable Metrics/PerceivedComplexity
128
+ def connect
129
+ @state = ClientState::START
130
+ loop do
131
+ case @state
132
+ when ClientState::START
133
+ send_client_hello
134
+ if use_early_data?
135
+ @early_data_write_cipher \
136
+ = gen_cipher(@settings[:psk_cipher_suite],
137
+ @key_schedule.early_data_write_key,
138
+ @key_schedule.early_data_write_iv)
139
+ send_early_data
140
+ end
141
+
142
+ @state = ClientState::WAIT_SH
143
+ when ClientState::WAIT_SH
144
+ sh = recv_server_hello
145
+ terminate(:illegal_parameter) unless valid_sh_legacy_version?
146
+ terminate(:illegal_parameter) unless valid_sh_legacy_session_id_echo?
147
+ terminate(:illegal_parameter) unless valid_sh_cipher_suite?
148
+ terminate(:illegal_parameter) unless valid_sh_compression_method?
149
+ # only TLS 1.3
150
+ terminate(:protocol_version) unless negotiated_tls_1_3?
151
+
152
+ if sh.hrr?
153
+ terminate(:unexpected_message) if received_2nd_hrr?
154
+
155
+ @transcript[CH1] = @transcript.delete(CH)
156
+ @transcript[HRR] = @transcript.delete(SH)
157
+ terminate(:unsupported_extension) \
158
+ unless offered_ch_extensions?(sh.extensions, HRR)
159
+ terminate(:illegal_parameter) unless valid_hrr_key_share?
160
+
161
+ send_new_client_hello
162
+ @state = ClientState::WAIT_SH
163
+ next
164
+ end
165
+
166
+ terminate(:unsupported_extension) \
167
+ unless offered_ch_extensions?(sh.extensions)
168
+ terminate(:illegal_parameter) \
169
+ if @transcript.include?(HRR) &&
170
+ neq_hrr_cipher_suite?(sh.cipher_suite)
171
+ versions \
172
+ = sh.extensions[Message::ExtensionType::SUPPORTED_VERSIONS].versions
173
+ terminate(:illegal_parameter) \
174
+ if @transcript.include?(HRR) &&
175
+ neq_hrr_supported_versions?(versions)
176
+
177
+ @psk = nil \
178
+ unless sh.extensions
179
+ .include?(Message::ExtensionType::PRE_SHARED_KEY)
180
+ terminate(:illegal_parameter) unless valid_sh_key_share?
181
+
182
+ kse = sh.extensions[Message::ExtensionType::KEY_SHARE]
183
+ .key_share_entry.first
184
+ key_exchange = kse.key_exchange
185
+ group = kse.group
186
+ priv_key = @priv_keys[group]
187
+ shared_key = gen_shared_secret(key_exchange, priv_key, group)
188
+ @cipher_suite = sh.cipher_suite
189
+ @key_schedule = KeySchedule.new(psk: @psk,
190
+ shared_secret: shared_key,
191
+ cipher_suite: @cipher_suite,
192
+ transcript: @transcript)
193
+ @write_cipher = gen_cipher(@cipher_suite,
194
+ @key_schedule.client_handshake_write_key,
195
+ @key_schedule.client_handshake_write_iv)
196
+ @read_cipher = gen_cipher(@cipher_suite,
197
+ @key_schedule.server_handshake_write_key,
198
+ @key_schedule.server_handshake_write_iv)
199
+ @state = ClientState::WAIT_EE
200
+ when ClientState::WAIT_EE
201
+ ee = recv_encrypted_extensions
202
+ terminate(:illegal_parameter) if ee.any_forbidden_extensions?
203
+ terminate(:unsupported_extension) \
204
+ unless offered_ch_extensions?(ee.extensions)
205
+
206
+ rsl = ee.extensions[Message::ExtensionType::RECORD_SIZE_LIMIT]
207
+ @send_record_size = rsl.record_size_limit unless rsl.nil?
208
+
209
+ @accepted_early_data = true \
210
+ if ee.extensions.include?(Message::ExtensionType::EARLY_DATA)
211
+
212
+ @state = ClientState::WAIT_CERT_CR
213
+ @state = ClientState::WAIT_FINISHED unless @psk.nil?
214
+ when ClientState::WAIT_CERT_CR
215
+ message = recv_message
216
+ if message.msg_type == Message::HandshakeType::CERTIFICATE
217
+ @transcript[CT] = ct = message
218
+ terminate(:unsupported_extension) \
219
+ unless ct.certificate_list.map(&:extensions)
220
+ .all? { |ex| offered_ch_extensions?(ex) }
221
+
222
+ terminate(:certificate_unknown) \
223
+ unless certified_certificate?(ct.certificate_list,
224
+ @settings[:ca_file], @hostname)
225
+
226
+ @state = ClientState::WAIT_CV
227
+ elsif message.msg_type == Message::HandshakeType::CERTIFICATE_REQUEST
228
+ @transcript[CR] = message
229
+ # TODO: client authentication
230
+ @state = ClientState::WAIT_CERT
231
+ else
232
+ terminate(:unexpected_message)
233
+ end
234
+ when ClientState::WAIT_CERT
235
+ ct = recv_certificate
236
+ terminate(:unsupported_extension) \
237
+ unless ct.certificate_list.map(&:extensions)
238
+ .all? { |ex| offered_ch_extensions?(ex) }
239
+
240
+ terminate(:certificate_unknown) \
241
+ unless certified_certificate?(ct.certificate_list,
242
+ @settings[:ca_file], @hostname)
243
+
244
+ @state = ClientState::WAIT_CV
245
+ when ClientState::WAIT_CV
246
+ recv_certificate_verify
247
+ terminate(:decrypt_error) unless verify_certificate_verify
248
+ @state = ClientState::WAIT_FINISHED
249
+ when ClientState::WAIT_FINISHED
250
+ recv_finished
251
+ terminate(:decrypt_error) unless verify_finished
252
+ send_ccs # compatibility mode
253
+ send_eoed if use_early_data? && accepted_early_data?
254
+ # TODO: Send Certificate [+ CertificateVerify]
255
+ send_finished
256
+ @write_cipher = gen_cipher(@cipher_suite,
257
+ @key_schedule.client_application_write_key,
258
+ @key_schedule.client_application_write_iv)
259
+ @read_cipher = gen_cipher(@cipher_suite,
260
+ @key_schedule.server_application_write_key,
261
+ @key_schedule.server_application_write_iv)
262
+ @state = ClientState::CONNECTED
263
+ when ClientState::CONNECTED
264
+ break
265
+ end
266
+ end
267
+ end
268
+ # rubocop: enable Metrics/AbcSize
269
+ # rubocop: enable Metrics/BlockLength
270
+ # rubocop: enable Metrics/CyclomaticComplexity
271
+ # rubocop: enable Metrics/MethodLength
272
+ # rubocop: enable Metrics/PerceivedComplexity
273
+
274
+ # @param binary [String]
275
+ #
276
+ # @raise [TTTLS13::Error::ConfigError]
277
+ def early_data(binary)
278
+ raise Error::ConfigError unless @state == INITIAL && use_psk?
279
+
280
+ @early_data = binary
281
+ end
282
+
283
+ # @return [Boolean]
284
+ def accepted_early_data?
285
+ @accepted_early_data
286
+ end
287
+
288
+ private
289
+
290
+ DOWNGRADE_PROTECTION_TLS_1_2 = "\x44\x4F\x57\x4E\x47\x52\x44\x01"
291
+ DOWNGRADE_PROTECTION_TLS_1_1 = "\x44\x4F\x57\x4E\x47\x52\x44\x00"
292
+
293
+ # @return [Boolean]
294
+ # rubocop: disable Metrics/AbcSize
295
+ # rubocop: disable Metrics/CyclomaticComplexity
296
+ # rubocop: disable Metrics/PerceivedComplexity
297
+ def valid_settings?
298
+ cs = CipherSuite
299
+ defined_cipher_suites = cs.constants.map { |c| cs.const_get(c) }
300
+ return false \
301
+ unless (@settings[:cipher_suites] - defined_cipher_suites).empty?
302
+
303
+ sa = @settings[:signature_algorithms]
304
+ ss = SignatureScheme
305
+ defined_signature_schemes = ss.constants.map { |c| ss.const_get(c) }
306
+ return false \
307
+ unless (sa - defined_signature_schemes).empty?
308
+
309
+ sac = @settings[:signature_algorithms_cert] || []
310
+ return false \
311
+ unless (sac - defined_signature_schemes).empty?
312
+
313
+ sg = @settings[:supported_groups]
314
+ ng = Message::Extension::NamedGroup
315
+ defined_named_groups = ng.constants.map { |c| ng.const_get(c) }
316
+ return false \
317
+ unless (sg - defined_named_groups).empty?
318
+
319
+ ksg = @settings[:key_share_groups]
320
+ return false unless ksg.nil? || ((ksg - sg).empty? &&
321
+ sg.select { |g| ksg.include?(g) } == ksg)
322
+
323
+ true
324
+ end
325
+ # rubocop: enable Metrics/AbcSize
326
+ # rubocop: enable Metrics/CyclomaticComplexity
327
+ # rubocop: enable Metrics/PerceivedComplexity
328
+
329
+ # @return [Boolean]
330
+ def use_psk?
331
+ !@settings[:ticket].nil? &&
332
+ !@settings[:resumption_master_secret].nil? &&
333
+ !@settings[:psk_cipher_suite].nil? &&
334
+ !@settings[:ticket_nonce].nil? &&
335
+ !@settings[:ticket_age_add].nil? &&
336
+ !@settings[:ticket_timestamp].nil?
337
+ end
338
+
339
+ # @return [Boolean]
340
+ def use_early_data?
341
+ !(@early_data.nil? || @early_data.empty?)
342
+ end
343
+
344
+ def send_early_data
345
+ ap = Message::ApplicationData.new(@early_data)
346
+ ap_record = Message::Record.new(
347
+ type: Message::ContentType::APPLICATION_DATA,
348
+ legacy_record_version: Message::ProtocolVersion::TLS_1_2,
349
+ messages: [ap],
350
+ cipher: @early_data_write_cipher
351
+ )
352
+ send_record(ap_record)
353
+ end
354
+
355
+ # @param resumption_master_secret [String]
356
+ # @param ticket_nonce [String]
357
+ # @param digest [String] name of digest algorithm
358
+ #
359
+ # @return [String]
360
+ def gen_psk_from_nst(resumption_master_secret, ticket_nonce, digest)
361
+ hash_len = OpenSSL::Digest.new(digest).digest_length
362
+ info = hash_len.to_uint16
363
+ info += 'tls13 resumption'.prefix_uint8_length
364
+ info += ticket_nonce.prefix_uint8_length
365
+ KeySchedule.hkdf_expand(resumption_master_secret, info, hash_len, digest)
366
+ end
367
+
368
+ # @return [TTTLS13::Message::Extensions]
369
+ # rubocop: disable Metrics/AbcSize
370
+ # rubocop: disable Metrics/CyclomaticComplexity
371
+ def gen_extensions
372
+ exs = []
373
+ # supported_versions: only TLS 1.3
374
+ exs << Message::Extension::SupportedVersions.new(
375
+ msg_type: Message::HandshakeType::CLIENT_HELLO
376
+ )
377
+
378
+ # signature_algorithms
379
+ exs << Message::Extension::SignatureAlgorithms.new(
380
+ @settings[:signature_algorithms]
381
+ )
382
+
383
+ # signature_algorithms_cert
384
+ if !@settings[:signature_algorithms_cert].nil? &&
385
+ !@settings[:signature_algorithms_cert].empty?
386
+ exs << Message::Extension::SignatureAlgorithmsCert.new(
387
+ @settings[:signature_algorithms_cert]
388
+ )
389
+ end
390
+
391
+ # supported_groups
392
+ groups = @settings[:supported_groups]
393
+ exs << Message::Extension::SupportedGroups.new(groups)
394
+ # key_share
395
+ ksg = @settings[:key_share_groups] || groups
396
+ key_share, priv_keys \
397
+ = Message::Extension::KeyShare.gen_ch_key_share(ksg)
398
+ exs << key_share
399
+ @priv_keys = priv_keys.merge(@priv_keys)
400
+
401
+ # server_name
402
+ exs << Message::Extension::ServerName.new(@hostname) \
403
+ if !@hostname.nil? && !@hostname.empty?
404
+
405
+ # early_data
406
+ exs << Message::Extension::EarlyDataIndication.new if use_early_data?
407
+
408
+ Message::Extensions.new(exs)
409
+ end
410
+ # rubocop: enable Metrics/AbcSize
411
+ # rubocop: enable Metrics/CyclomaticComplexity
412
+
413
+ # @return [TTTLS13::Message::ClientHello]
414
+ def send_client_hello
415
+ exs = gen_extensions
416
+ ch = Message::ClientHello.new(
417
+ cipher_suites: CipherSuites.new(@settings[:cipher_suites]),
418
+ extensions: exs
419
+ )
420
+ @transcript[CH] = ch
421
+
422
+ if use_psk?
423
+ # pre_shared_key && psk_key_exchange_modes
424
+ #
425
+ # In order to use PSKs, clients MUST also send a
426
+ # "psk_key_exchange_modes" extension.
427
+ #
428
+ # https://tools.ietf.org/html/rfc8446#section-4.2.9
429
+ pkem = Message::Extension::PskKeyExchangeModes.new(
430
+ [Message::Extension::PskKeyExchangeMode::PSK_DHE_KE]
431
+ )
432
+ ch.extensions[Message::ExtensionType::PSK_KEY_EXCHANGE_MODES] = pkem
433
+ # at the end, sign PSK binder
434
+ sign_psk_binder
435
+ end
436
+
437
+ send_handshakes(Message::ContentType::HANDSHAKE, [ch], @write_cipher)
438
+ end
439
+
440
+ # @return [String]
441
+ def sign_psk_binder
442
+ # pre_shared_key
443
+ #
444
+ # binder is computed as an HMAC over a transcript hash containing a
445
+ # partial ClientHello up to and including the
446
+ # PreSharedKeyExtension.identities field.
447
+ #
448
+ # https://tools.ietf.org/html/rfc8446#section-4.2.11.2
449
+ digest = CipherSuite.digest(@settings[:psk_cipher_suite])
450
+ hash_len = OpenSSL::Digest.new(digest).digest_length
451
+ dummy_binders = ["\x00" * hash_len]
452
+ psk = Message::Extension::PreSharedKey.new(
453
+ msg_type: Message::HandshakeType::CLIENT_HELLO,
454
+ offered_psks: Message::Extension::OfferedPsks.new(
455
+ identities: [Message::Extension::PskIdentity.new(
456
+ identity: @settings[:ticket],
457
+ obfuscated_ticket_age: calc_obfuscated_ticket_age
458
+ )],
459
+ binders: dummy_binders
460
+ )
461
+ )
462
+ @transcript[CH].extensions[Message::ExtensionType::PRE_SHARED_KEY] = psk
463
+
464
+ # TODO: ext binder
465
+ psk.offered_psks.binders[0] = do_sign_psk_binder(digest)
466
+ end
467
+
468
+ # @return [Integer]
469
+ def calc_obfuscated_ticket_age
470
+ # the "ticket_lifetime" field in the NewSessionTicket message is
471
+ # in seconds but the "obfuscated_ticket_age" is in milliseconds.
472
+ age = (Time.now.to_f * 1000).to_i - @settings[:ticket_timestamp] * 1000
473
+ (age + Convert.bin2i(@settings[:ticket_age_add])) % (2**32)
474
+ end
475
+
476
+ # NOTE:
477
+ # https://tools.ietf.org/html/rfc8446#section-4.1.2
478
+ #
479
+ # @return [TTTLS13::Message::ClientHello]
480
+ def send_new_client_hello
481
+ hrr_exs = @transcript[HRR].extensions
482
+ arr = []
483
+
484
+ # key_share
485
+ if hrr_exs.include?(Message::ExtensionType::KEY_SHARE)
486
+ group = hrr_exs[Message::ExtensionType::KEY_SHARE].key_share_entry
487
+ .first.group
488
+ key_share, priv_keys \
489
+ = Message::Extension::KeyShare.gen_ch_key_share([group])
490
+ arr << key_share
491
+ @priv_keys = priv_keys.merge(@priv_keys)
492
+ end
493
+
494
+ # cookie
495
+ #
496
+ # When sending a HelloRetryRequest, the server MAY provide a "cookie"
497
+ # extension to the client... When sending the new ClientHello, the client
498
+ # MUST copy the contents of the extension received in the
499
+ # HelloRetryRequest into a "cookie" extension in the new ClientHello.
500
+ #
501
+ # https://tools.ietf.org/html/rfc8446#section-4.2.2
502
+ if hrr_exs.include?(Message::ExtensionType::COOKIE)
503
+ arr << hrr_exs[Message::ExtensionType::COOKIE]
504
+ end
505
+
506
+ # early_data
507
+ ch1 = @transcript[CH1]
508
+ new_exs = ch1.extensions.merge(Message::Extensions.new(arr))
509
+ new_exs.delete(Message::ExtensionType::EARLY_DATA)
510
+ ch = Message::ClientHello.new(
511
+ legacy_version: ch1.legacy_version,
512
+ random: ch1.random,
513
+ legacy_session_id: ch1.legacy_session_id,
514
+ cipher_suites: ch1.cipher_suites,
515
+ legacy_compression_methods: ch1.legacy_compression_methods,
516
+ extensions: new_exs
517
+ )
518
+ send_handshakes(Message::ContentType::HANDSHAKE, [ch], @write_cipher)
519
+ @transcript[CH] = ch
520
+ end
521
+
522
+ # @raise [TTTLS13::Error::ErrorAlerts]
523
+ #
524
+ # @return [TTTLS13::Message::ServerHello]
525
+ def recv_server_hello
526
+ sh = recv_message
527
+ terminate(:unexpected_message) unless sh.is_a?(Message::ServerHello)
528
+
529
+ @transcript[SH] = sh
530
+ end
531
+
532
+ # @raise [TTTLS13::Error::ErrorAlerts]
533
+ #
534
+ # @return [TTTLS13::Message::EncryptedExtensions]
535
+ def recv_encrypted_extensions
536
+ ee = recv_message
537
+ terminate(:unexpected_message) \
538
+ unless ee.is_a?(Message::EncryptedExtensions)
539
+
540
+ @transcript[EE] = ee
541
+ end
542
+
543
+ # @raise [TTTLS13::Error::ErrorAlerts]
544
+ #
545
+ # @return [TTTLS13::Message::Certificate]
546
+ def recv_certificate
547
+ ct = recv_message
548
+ terminate(:unexpected_message) unless ct.is_a?(Message::Certificate)
549
+
550
+ @transcript[CT] = ct
551
+ end
552
+
553
+ # @raise [TTTLS13::Error::ErrorAlerts]
554
+ #
555
+ # @return [TTTLS13::Message::CertificateVerify]
556
+ def recv_certificate_verify
557
+ cv = recv_message
558
+ terminate(:unexpected_message) unless cv.is_a?(Message::CertificateVerify)
559
+
560
+ @transcript[CV] = cv
561
+ end
562
+
563
+ # @raise [TTTLS13::Error::ErrorAlerts]
564
+ #
565
+ # @return [TTTLS13::Message::Finished]
566
+ def recv_finished
567
+ sf = recv_message
568
+ terminate(:unexpected_message) unless sf.is_a?(Message::Finished)
569
+
570
+ @transcript[SF] = sf
571
+ end
572
+
573
+ # @return [TTTLS13::Message::Finished]
574
+ def send_finished
575
+ cf = Message::Finished.new(sign_finished)
576
+ send_handshakes(Message::ContentType::APPLICATION_DATA, [cf],
577
+ @write_cipher)
578
+
579
+ @transcript[CF] = cf
580
+ end
581
+
582
+ # @return [TTTLS13::Message::EndOfEarlyData]
583
+ def send_eoed
584
+ eoed = Message::EndOfEarlyData.new
585
+ send_handshakes(Message::ContentType::APPLICATION_DATA, [eoed],
586
+ @early_data_write_cipher)
587
+
588
+ @transcript[EOED] = eoed
589
+ end
590
+
591
+ # @return [Boolean]
592
+ def verify_certificate_verify
593
+ ct = @transcript[CT]
594
+ certificate_pem = ct.certificate_list.first.cert_data.to_pem
595
+ cv = @transcript[CV]
596
+ signature_scheme = cv.signature_scheme
597
+ signature = cv.signature
598
+ context = 'TLS 1.3, server CertificateVerify'
599
+ do_verify_certificate_verify(certificate_pem: certificate_pem,
600
+ signature_scheme: signature_scheme,
601
+ signature: signature,
602
+ context: context,
603
+ handshake_context_end: CT)
604
+ end
605
+
606
+ # @return [String]
607
+ def sign_finished
608
+ digest = CipherSuite.digest(@cipher_suite)
609
+ finished_key = @key_schedule.client_finished_key
610
+ do_sign_finished(digest: digest,
611
+ finished_key: finished_key,
612
+ handshake_context_end: EOED)
613
+ end
614
+
615
+ # @return [Boolean]
616
+ def verify_finished
617
+ digest = CipherSuite.digest(@cipher_suite)
618
+ finished_key = @key_schedule.server_finished_key
619
+ signature = @transcript[SF].verify_data
620
+ do_verify_finished(digest: digest,
621
+ finished_key: finished_key,
622
+ handshake_context_end: CV,
623
+ signature: signature)
624
+ end
625
+
626
+ # NOTE:
627
+ # This implementation supports only TLS 1.3,
628
+ # so negotiated_tls_1_3? assumes that it sent ClientHello with:
629
+ # 1. supported_versions == ["\x03\x04"]
630
+ # 2. legacy_versions == ["\x03\x03"]
631
+ #
632
+ # @raise [TTTLS13::Error::ErrorAlerts]
633
+ #
634
+ # @return [Boolean]
635
+ def negotiated_tls_1_3?
636
+ sh = @transcript[SH]
637
+ sh_sv = sh.extensions[Message::ExtensionType::SUPPORTED_VERSIONS]
638
+ &.versions
639
+ sh_r8 = sh.random[-8..]
640
+ if sh_sv&.first == Message::ProtocolVersion::TLS_1_3 &&
641
+ sh_r8 != DOWNGRADE_PROTECTION_TLS_1_2 &&
642
+ sh_r8 != DOWNGRADE_PROTECTION_TLS_1_1
643
+ true
644
+ elsif sh_sv.nil?
645
+ false
646
+ else
647
+ terminate(:illegal_parameter)
648
+ end
649
+ end
650
+
651
+ # @return [Boolean]
652
+ def valid_sh_legacy_version?
653
+ @transcript[CH].legacy_version ==
654
+ @transcript[SH].legacy_version
655
+ end
656
+
657
+ # @return [Boolean]
658
+ def valid_sh_legacy_session_id_echo?
659
+ @transcript[CH].legacy_session_id ==
660
+ @transcript[SH].legacy_session_id_echo
661
+ end
662
+
663
+ # @return [Boolean]
664
+ def valid_sh_cipher_suite?
665
+ @transcript[CH].cipher_suites.include?(@transcript[SH].cipher_suite)
666
+ end
667
+
668
+ # @return [Boolean]
669
+ def valid_sh_compression_method?
670
+ @transcript[SH].legacy_compression_method == "\x00"
671
+ end
672
+
673
+ # @param extensions [TTTLS13::Message::Extensions]
674
+ # @param transcript_index [Integer]
675
+ #
676
+ # @return [Boolean]
677
+ def offered_ch_extensions?(extensions, transcript_index = nil)
678
+ keys = extensions.keys
679
+ if transcript_index == HRR
680
+ keys -= @transcript[CH1].extensions.keys
681
+ keys -= [Message::ExtensionType::COOKIE]
682
+ else
683
+ keys -= @transcript[CH].extensions.keys
684
+ end
685
+ keys.empty?
686
+ end
687
+
688
+ # @return [Boolean]
689
+ def received_2nd_hrr?
690
+ @transcript.include?(HRR)
691
+ end
692
+
693
+ # @param cipher_suite [TTTLS13::CipherSuite]
694
+ #
695
+ # @return [Boolean]
696
+ def neq_hrr_cipher_suite?(cipher_suite)
697
+ cipher_suite != @transcript[HRR].cipher_suite
698
+ end
699
+
700
+ # @param versions [Array of TTTLS13::Message::ProtocolVersion]
701
+ #
702
+ # @return [Boolean]
703
+ def neq_hrr_supported_versions?(versions)
704
+ hrr = @transcript[HRR]
705
+ versions != hrr.extensions[Message::ExtensionType::SUPPORTED_VERSIONS]
706
+ .versions
707
+ end
708
+
709
+ # @return [Boolean]
710
+ def valid_sh_key_share?
711
+ offered = @transcript[CH].extensions[Message::ExtensionType::KEY_SHARE]
712
+ .key_share_entry.map(&:group)
713
+ selected = @transcript[CH].extensions[Message::ExtensionType::KEY_SHARE]
714
+ .key_share_entry.first.group
715
+ offered.include?(selected)
716
+ end
717
+
718
+ # @return [Boolean]
719
+ def valid_hrr_key_share?
720
+ # TODO: pre_shared_key
721
+ ch1_exs = @transcript[CH1].extensions
722
+ ngl = ch1_exs[Message::ExtensionType::SUPPORTED_GROUPS].named_group_list
723
+ group = @transcript[HRR].extensions[Message::ExtensionType::KEY_SHARE]
724
+ .key_share_entry.first.group
725
+ return false unless ngl.include?(group)
726
+
727
+ kse = ch1_exs[Message::ExtensionType::KEY_SHARE].key_share_entry
728
+ return false if !kse.empty? && kse.map(&:group).include?(group)
729
+
730
+ true
731
+ end
732
+
733
+ # @param nst [TTTLS13::Message::NewSessionTicket]
734
+ #
735
+ # @raise [TTTLS13::Error::ErrorAlerts]
736
+ def process_new_session_ticket(nst)
737
+ super(nst)
738
+
739
+ rms = @key_schedule.resumption_master_secret
740
+ cs = @cipher_suite
741
+ @settings[:process_new_session_ticket]&.call(nst, rms, cs)
742
+ end
743
+ end
744
+ # rubocop: enable Metrics/ClassLength
745
+ end