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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +8 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +133 -0
- data/example/helper.rb +17 -0
- data/example/https_client.rb +32 -0
- data/example/https_client_using_0rtt.rb +64 -0
- data/example/https_client_using_hrr.rb +35 -0
- data/example/https_client_using_ticket.rb +56 -0
- data/lib/tttls1.3/cipher_suites.rb +102 -0
- data/lib/tttls1.3/client.rb +745 -0
- data/lib/tttls1.3/connection.rb +380 -0
- data/lib/tttls1.3/cryptograph/aead.rb +118 -0
- data/lib/tttls1.3/cryptograph/passer.rb +22 -0
- data/lib/tttls1.3/cryptograph.rb +3 -0
- data/lib/tttls1.3/error.rb +22 -0
- data/lib/tttls1.3/key_schedule.rb +242 -0
- data/lib/tttls1.3/message/alert.rb +86 -0
- data/lib/tttls1.3/message/application_data.rb +27 -0
- data/lib/tttls1.3/message/certificate.rb +121 -0
- data/lib/tttls1.3/message/certificate_verify.rb +59 -0
- data/lib/tttls1.3/message/change_cipher_spec.rb +26 -0
- data/lib/tttls1.3/message/client_hello.rb +100 -0
- data/lib/tttls1.3/message/encrypted_extensions.rb +65 -0
- data/lib/tttls1.3/message/end_of_early_data.rb +29 -0
- data/lib/tttls1.3/message/extension/alpn.rb +70 -0
- data/lib/tttls1.3/message/extension/cookie.rb +47 -0
- data/lib/tttls1.3/message/extension/early_data_indication.rb +58 -0
- data/lib/tttls1.3/message/extension/key_share.rb +236 -0
- data/lib/tttls1.3/message/extension/pre_shared_key.rb +205 -0
- data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +54 -0
- data/lib/tttls1.3/message/extension/record_size_limit.rb +46 -0
- data/lib/tttls1.3/message/extension/server_name.rb +91 -0
- data/lib/tttls1.3/message/extension/signature_algorithms.rb +69 -0
- data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +25 -0
- data/lib/tttls1.3/message/extension/status_request.rb +106 -0
- data/lib/tttls1.3/message/extension/supported_groups.rb +145 -0
- data/lib/tttls1.3/message/extension/supported_versions.rb +98 -0
- data/lib/tttls1.3/message/extension/unknown_extension.rb +38 -0
- data/lib/tttls1.3/message/extensions.rb +173 -0
- data/lib/tttls1.3/message/finished.rb +44 -0
- data/lib/tttls1.3/message/new_session_ticket.rb +89 -0
- data/lib/tttls1.3/message/record.rb +232 -0
- data/lib/tttls1.3/message/server_hello.rb +116 -0
- data/lib/tttls1.3/message.rb +48 -0
- data/lib/tttls1.3/sequence_number.rb +31 -0
- data/lib/tttls1.3/signature_scheme.rb +31 -0
- data/lib/tttls1.3/transcript.rb +69 -0
- data/lib/tttls1.3/utils.rb +91 -0
- data/lib/tttls1.3/version.rb +5 -0
- data/lib/tttls1.3.rb +16 -0
- data/spec/aead_spec.rb +95 -0
- data/spec/alert_spec.rb +54 -0
- data/spec/alpn_spec.rb +55 -0
- data/spec/application_data_spec.rb +26 -0
- data/spec/certificate_spec.rb +55 -0
- data/spec/certificate_verify_spec.rb +51 -0
- data/spec/change_cipher_spec_spec.rb +26 -0
- data/spec/cipher_suites_spec.rb +39 -0
- data/spec/client_hello_spec.rb +83 -0
- data/spec/client_spec.rb +319 -0
- data/spec/connection_spec.rb +114 -0
- data/spec/cookie_spec.rb +98 -0
- data/spec/early_data_indication_spec.rb +64 -0
- data/spec/encrypted_extensions_spec.rb +94 -0
- data/spec/error_spec.rb +18 -0
- data/spec/extensions_spec.rb +170 -0
- data/spec/finished_spec.rb +55 -0
- data/spec/key_schedule_spec.rb +198 -0
- data/spec/key_share_spec.rb +199 -0
- data/spec/new_session_ticket_spec.rb +80 -0
- data/spec/pre_shared_key_spec.rb +167 -0
- data/spec/psk_key_exchange_modes_spec.rb +45 -0
- data/spec/record_size_limit_spec.rb +61 -0
- data/spec/record_spec.rb +105 -0
- data/spec/server_hello_spec.rb +101 -0
- data/spec/server_name_spec.rb +110 -0
- data/spec/signature_algorithms_cert_spec.rb +73 -0
- data/spec/signature_algorithms_spec.rb +100 -0
- data/spec/spec_helper.rb +872 -0
- data/spec/status_request_spec.rb +73 -0
- data/spec/supported_groups_spec.rb +79 -0
- data/spec/supported_versions_spec.rb +136 -0
- data/spec/transcript_spec.rb +69 -0
- data/spec/unknown_extension_spec.rb +90 -0
- data/spec/utils_spec.rb +215 -0
- data/tttls1.3.gemspec +25 -0
- 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
|