tttls1.3 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|