tttls1.3 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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