tttls1.3 0.2.1 → 0.2.2
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 +4 -4
- data/README.md +1 -0
- data/Rakefile +3 -3
- data/example/https_client_using_hrr_and_ticket.rb +40 -0
- data/interop/client_spec.rb +15 -0
- data/interop/server_spec.rb +8 -0
- data/lib/tttls1.3/client.rb +265 -272
- data/lib/tttls1.3/connection.rb +85 -62
- data/lib/tttls1.3/message/certificate.rb +1 -1
- data/lib/tttls1.3/message/client_hello.rb +26 -1
- data/lib/tttls1.3/message/encrypted_extensions.rb +1 -1
- data/lib/tttls1.3/message/new_session_ticket.rb +1 -1
- data/lib/tttls1.3/message/server_hello.rb +21 -1
- data/lib/tttls1.3/server.rb +179 -157
- data/lib/tttls1.3/version.rb +1 -1
- data/spec/certificate_spec.rb +4 -4
- data/spec/client_hello_spec.rb +3 -0
- data/spec/client_spec.rb +96 -157
- data/spec/connection_spec.rb +32 -23
- data/spec/encrypted_extensions_spec.rb +4 -4
- data/spec/fixtures/rsa_ca.crt +16 -27
- data/spec/fixtures/rsa_ca.key +25 -49
- data/spec/fixtures/rsa_rsa.crt +16 -21
- data/spec/fixtures/rsa_rsa.key +25 -25
- data/spec/fixtures/rsa_rsassaPss.crt +20 -0
- data/spec/fixtures/rsa_rsassaPss.key +27 -0
- data/spec/fixtures/rsa_secp256r1.crt +12 -17
- data/spec/fixtures/rsa_secp256r1.key +3 -3
- data/spec/fixtures/rsa_secp384r1.crt +12 -17
- data/spec/fixtures/rsa_secp384r1.key +4 -4
- data/spec/fixtures/rsa_secp521r1.crt +13 -18
- data/spec/fixtures/rsa_secp521r1.key +5 -5
- data/spec/server_hello_spec.rb +60 -0
- data/spec/server_spec.rb +79 -60
- metadata +7 -2
data/lib/tttls1.3/connection.rb
CHANGED
@@ -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
|
-
@
|
17
|
-
@
|
18
|
-
@
|
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
|
-
@
|
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, @
|
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
|
103
|
-
def send_handshakes(type, messages,
|
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:
|
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
|
124
|
-
def send_application_data(message,
|
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:
|
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 @
|
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: @
|
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,
|
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
|
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 =
|
228
|
-
OpenSSL::HMAC.digest(digest,
|
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
|
259
|
+
# @param key [OpenSSL::PKey::PKey]
|
232
260
|
# @param signature_scheme [TTTLS13::SignatureScheme]
|
233
261
|
# @param context [String]
|
234
|
-
# @param
|
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(
|
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
|
-
|
253
|
-
|
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
|
-
|
258
|
-
|
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
|
-
|
263
|
-
|
287
|
+
key.sign_pss('SHA512', content, salt_length: :digest,
|
288
|
+
mgf1_hash: 'SHA512')
|
264
289
|
when SignatureScheme::ECDSA_SECP256R1_SHA256
|
265
|
-
|
290
|
+
key.sign('SHA256', content)
|
266
291
|
when SignatureScheme::ECDSA_SECP384R1_SHA384
|
267
|
-
|
292
|
+
key.sign('SHA384', content)
|
268
293
|
when SignatureScheme::ECDSA_SECP521R1_SHA512
|
269
|
-
|
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
|
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
|
347
|
+
# @param hash [String]
|
326
348
|
#
|
327
349
|
# @return [String]
|
328
|
-
def
|
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
|
336
|
-
# @param signature [String]
|
357
|
+
# @param hash [String]
|
337
358
|
#
|
338
359
|
# @return [Boolean]
|
339
|
-
def
|
340
|
-
|
341
|
-
|
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
|
-
(
|
375
|
-
|
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]
|
@@ -127,12 +127,37 @@ module TTTLS13
|
|
127
127
|
# rubocop: enable Metrics/MethodLength
|
128
128
|
|
129
129
|
# @return [Boolean]
|
130
|
-
def
|
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
|
@@ -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
|
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
|