tttls1.3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,380 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
INITIAL = 0
|
6
|
+
EOF = -1
|
7
|
+
|
8
|
+
# rubocop: disable Metrics/ClassLength
|
9
|
+
class Connection
|
10
|
+
# @param socket [Socket]
|
11
|
+
def initialize(socket)
|
12
|
+
@socket = socket
|
13
|
+
@endpoint = nil # Symbol or String, :client or :server
|
14
|
+
@key_schedule = nil # TTTLS13::KeySchedule
|
15
|
+
@priv_keys = {} # Hash of NamedGroup => OpenSSL::PKey::$Object
|
16
|
+
@read_cipher = Cryptograph::Passer.new
|
17
|
+
@write_cipher = Cryptograph::Passer.new
|
18
|
+
@transcript = Transcript.new
|
19
|
+
@message_queue = [] # Array of TTTLS13::Message::$Object
|
20
|
+
@binary_buffer = '' # deposit Record.surplus_binary
|
21
|
+
@cipher_suite = nil # TTTLS13::CipherSuite
|
22
|
+
@notyet_application_secret = true
|
23
|
+
@state = 0 # ClientState or ServerState
|
24
|
+
@send_record_size = Message::DEFAULT_RECORD_SIZE_LIMIT
|
25
|
+
@psk = nil # String
|
26
|
+
end
|
27
|
+
|
28
|
+
# @raise [TTTLS13::Error::ConfigError]
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
def read
|
32
|
+
# secure channel has not established yet
|
33
|
+
raise Error::ConfigError \
|
34
|
+
unless @endpoint == :client && @state == ClientState::CONNECTED
|
35
|
+
return '' if @state == EOF
|
36
|
+
|
37
|
+
message = nil
|
38
|
+
loop do
|
39
|
+
message = recv_message
|
40
|
+
# At any time after the server has received the client Finished
|
41
|
+
# message, it MAY send a NewSessionTicket message.
|
42
|
+
break unless message.is_a?(Message::NewSessionTicket)
|
43
|
+
|
44
|
+
process_new_session_ticket(message)
|
45
|
+
end
|
46
|
+
return '' if message.nil?
|
47
|
+
|
48
|
+
message.fragment
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Boolean]
|
52
|
+
def eof?
|
53
|
+
@state == EOF
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param binary [String]
|
57
|
+
#
|
58
|
+
# @raise [TTTLS13::Error::ConfigError]
|
59
|
+
def write(binary)
|
60
|
+
# secure channel has not established yet
|
61
|
+
raise Error::ConfigError \
|
62
|
+
unless @endpoint == :client && @state == ClientState::CONNECTED
|
63
|
+
|
64
|
+
ap = Message::ApplicationData.new(binary)
|
65
|
+
send_application_data(ap, @write_cipher)
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
send_alert(:close_notify)
|
70
|
+
@state = EOF
|
71
|
+
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# @param cipher_suite [TTTLS13::CipherSuite]
|
78
|
+
# @param write_key [String]
|
79
|
+
# @param write_iv [String]
|
80
|
+
#
|
81
|
+
# @return [TTTLS13::Cryptograph::Aead]
|
82
|
+
def gen_cipher(cipher_suite, write_key, write_iv)
|
83
|
+
seq_num = SequenceNumber.new
|
84
|
+
Cryptograph::Aead.new(
|
85
|
+
cipher_suite: cipher_suite,
|
86
|
+
write_key: write_key,
|
87
|
+
write_iv: write_iv,
|
88
|
+
sequence_number: seq_num
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param type [TTTLS13::Message::ContentType]
|
93
|
+
# @param messages [Array of TTTLS13::Message::$Object] handshake messages
|
94
|
+
# @param write_cipher [TTTLS13::Cryptograph::Aead]
|
95
|
+
def send_handshakes(type, messages, write_cipher)
|
96
|
+
record = Message::Record.new(
|
97
|
+
type: type,
|
98
|
+
messages: messages,
|
99
|
+
cipher: write_cipher
|
100
|
+
)
|
101
|
+
send_record(record)
|
102
|
+
end
|
103
|
+
|
104
|
+
def send_ccs
|
105
|
+
ccs_record = Message::Record.new(
|
106
|
+
type: Message::ContentType::CCS,
|
107
|
+
legacy_record_version: Message::ProtocolVersion::TLS_1_2,
|
108
|
+
messages: [Message::ChangeCipherSpec.new],
|
109
|
+
cipher: Cryptograph::Passer.new
|
110
|
+
)
|
111
|
+
send_record(ccs_record)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param message [TTTLS13::Message::ApplicationData]
|
115
|
+
# @param write_cipher [TTTLS13::Cryptograph::Aead]
|
116
|
+
def send_application_data(message, write_cipher)
|
117
|
+
ap_record = Message::Record.new(
|
118
|
+
type: Message::ContentType::APPLICATION_DATA,
|
119
|
+
legacy_record_version: Message::ProtocolVersion::TLS_1_2,
|
120
|
+
messages: [message],
|
121
|
+
cipher: write_cipher
|
122
|
+
)
|
123
|
+
send_record(ap_record)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @param symbol [Symbol] key of ALERT_DESCRIPTION
|
127
|
+
def send_alert(symbol)
|
128
|
+
message = Message::Alert.new(
|
129
|
+
description: Message::ALERT_DESCRIPTION[symbol]
|
130
|
+
)
|
131
|
+
type = Message::ContentType::ALERT
|
132
|
+
type = Message::ContentType::APPLICATION_DATA \
|
133
|
+
if @write_cipher.is_a?(Cryptograph::Aead)
|
134
|
+
alert_record = Message::Record.new(
|
135
|
+
type: type,
|
136
|
+
legacy_record_version: Message::ProtocolVersion::TLS_1_2,
|
137
|
+
messages: [message],
|
138
|
+
cipher: @write_cipher
|
139
|
+
)
|
140
|
+
send_record(alert_record)
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param record [TTTLS13::Message::Record]
|
144
|
+
def send_record(record)
|
145
|
+
@socket.write(record.serialize(@send_record_size))
|
146
|
+
end
|
147
|
+
|
148
|
+
# @raise [TTTLS13::Error::ErrorAlerts
|
149
|
+
#
|
150
|
+
# @return [TTTLS13::Message::$Object]
|
151
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
152
|
+
def recv_message
|
153
|
+
return @message_queue.shift unless @message_queue.empty?
|
154
|
+
|
155
|
+
messages = nil
|
156
|
+
loop do
|
157
|
+
record = recv_record
|
158
|
+
case record.type
|
159
|
+
when Message::ContentType::HANDSHAKE,
|
160
|
+
Message::ContentType::APPLICATION_DATA
|
161
|
+
messages = record.messages
|
162
|
+
break unless messages.empty?
|
163
|
+
when Message::ContentType::CCS
|
164
|
+
terminate(:unexpected_message) unless ccs_receivable?
|
165
|
+
next
|
166
|
+
when Message::ContentType::ALERT
|
167
|
+
handle_received_alert(record.messages.first)
|
168
|
+
return nil
|
169
|
+
else
|
170
|
+
terminate(:unexpected_message)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
@message_queue += messages[1..]
|
175
|
+
message = messages.first
|
176
|
+
if message.is_a?(Message::Alert)
|
177
|
+
handle_received_alert(message)
|
178
|
+
return nil
|
179
|
+
end
|
180
|
+
|
181
|
+
message
|
182
|
+
end
|
183
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
184
|
+
|
185
|
+
# @return [TTTLS13::Message::Record]
|
186
|
+
def recv_record
|
187
|
+
binary = @socket.read(5)
|
188
|
+
record_len = Convert.bin2i(binary.slice(3, 2))
|
189
|
+
binary += @socket.read(record_len)
|
190
|
+
|
191
|
+
begin
|
192
|
+
buffer = @binary_buffer
|
193
|
+
record = Message::Record.deserialize(binary, @read_cipher, buffer)
|
194
|
+
@binary_buffer = record.surplus_binary
|
195
|
+
rescue Error::ErrorAlerts => e
|
196
|
+
terminate(e.message.to_sym)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Received a protected ccs, peer MUST abort the handshake.
|
200
|
+
if record.type == Message::ContentType::APPLICATION_DATA &&
|
201
|
+
record.messages.first.is_a?(Message::ChangeCipherSpec)
|
202
|
+
terminate(:unexpected_message)
|
203
|
+
end
|
204
|
+
|
205
|
+
record
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param digest [String] name of digest algorithm
|
209
|
+
#
|
210
|
+
# @return [String]
|
211
|
+
def do_sign_psk_binder(digest)
|
212
|
+
# TODO: ext binder
|
213
|
+
secret = @key_schedule.binder_key_res
|
214
|
+
hash_len = OpenSSL::Digest.new(digest).digest_length
|
215
|
+
# transcript-hash (CH1 + HRR +) truncated-CH
|
216
|
+
hash = @transcript.truncate_hash(digest, CH, hash_len + 3)
|
217
|
+
OpenSSL::HMAC.digest(digest, secret, hash)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @param certificate_pem [String]
|
221
|
+
# @param signature_scheme [TTTLS13::SignatureScheme]
|
222
|
+
# @param signature [String]
|
223
|
+
# @param context [String]
|
224
|
+
# @param handshake_context_end [Integer]
|
225
|
+
#
|
226
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
227
|
+
#
|
228
|
+
# @return [Boolean]
|
229
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
230
|
+
def do_verify_certificate_verify(certificate_pem:, signature_scheme:,
|
231
|
+
signature:, context:,
|
232
|
+
handshake_context_end:)
|
233
|
+
digest = CipherSuite.digest(@cipher_suite)
|
234
|
+
hash = @transcript.hash(digest, handshake_context_end)
|
235
|
+
content = "\x20" * 64 + context + "\x00" + hash
|
236
|
+
public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
|
237
|
+
|
238
|
+
case signature_scheme
|
239
|
+
when SignatureScheme::RSA_PSS_RSAE_SHA256,
|
240
|
+
SignatureScheme::RSA_PSS_PSS_SHA256
|
241
|
+
public_key.verify_pss('SHA256', signature, content, salt_length: :auto,
|
242
|
+
mgf1_hash: 'SHA256')
|
243
|
+
when SignatureScheme::RSA_PSS_RSAE_SHA384,
|
244
|
+
SignatureScheme::RSA_PSS_PSS_SHA384
|
245
|
+
public_key.verify_pss('SHA384', signature, content, salt_length: :auto,
|
246
|
+
mgf1_hash: 'SHA384')
|
247
|
+
when SignatureScheme::RSA_PSS_RSAE_SHA512,
|
248
|
+
SignatureScheme::RSA_PSS_PSS_SHA512
|
249
|
+
public_key.verify_pss('SHA512', signature, content, salt_length: :auto,
|
250
|
+
mgf1_hash: 'SHA512')
|
251
|
+
when SignatureScheme::RSA_PKCS1_SHA256,
|
252
|
+
SignatureScheme::ECDSA_SECP256R1_SHA256
|
253
|
+
public_key.verify('SHA256', signature, content)
|
254
|
+
when SignatureScheme::RSA_PKCS1_SHA384,
|
255
|
+
SignatureScheme::ECDSA_SECP384R1_SHA384
|
256
|
+
public_key.verify('SHA384', signature, content)
|
257
|
+
when SignatureScheme::RSA_PKCS1_SHA512,
|
258
|
+
SignatureScheme::ECDSA_SECP521R1_SHA512
|
259
|
+
public_key.verify('SHA512', signature, content)
|
260
|
+
else # TODO: ED25519, ED448
|
261
|
+
terminate(:internal_error)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
265
|
+
|
266
|
+
# @param digest [String] name of digest algorithm
|
267
|
+
# @param finished_key [String]
|
268
|
+
# @param handshake_context_end [Integer]
|
269
|
+
#
|
270
|
+
# @return [String]
|
271
|
+
def do_sign_finished(digest:, finished_key:, handshake_context_end:)
|
272
|
+
hash = @transcript.hash(digest, handshake_context_end)
|
273
|
+
OpenSSL::HMAC.digest(digest, finished_key, hash)
|
274
|
+
end
|
275
|
+
|
276
|
+
# @param digest [String] name of digest algorithm
|
277
|
+
# @param finished_key [String]
|
278
|
+
# @param handshake_context_end [Integer]
|
279
|
+
# @param signature [String]
|
280
|
+
#
|
281
|
+
# @return [Boolean]
|
282
|
+
def do_verify_finished(digest:, finished_key:, handshake_context_end:,
|
283
|
+
signature:)
|
284
|
+
do_sign_finished(
|
285
|
+
digest: digest,
|
286
|
+
finished_key: finished_key,
|
287
|
+
handshake_context_end: handshake_context_end
|
288
|
+
) == signature
|
289
|
+
end
|
290
|
+
|
291
|
+
# @param key_exchange [String]
|
292
|
+
# @param priv_key [OpenSSL::PKey::$Object]
|
293
|
+
# @param group [TTTLS13::Message::ExtensionType::NamedGroup]
|
294
|
+
#
|
295
|
+
# @return [String]
|
296
|
+
def gen_shared_secret(key_exchange, priv_key, group)
|
297
|
+
curve = Message::Extension::NamedGroup.curve_name(group)
|
298
|
+
terminate(:internal_error) if curve.nil?
|
299
|
+
|
300
|
+
pub_key = OpenSSL::PKey::EC::Point.new(
|
301
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
302
|
+
OpenSSL::BN.new(key_exchange, 2)
|
303
|
+
)
|
304
|
+
|
305
|
+
priv_key.dh_compute_key(pub_key)
|
306
|
+
end
|
307
|
+
|
308
|
+
# @return [Boolean]
|
309
|
+
#
|
310
|
+
# Received ccs before the first ClientHello message or after the peer's
|
311
|
+
# Finished message, peer MUST abort.
|
312
|
+
def ccs_receivable?
|
313
|
+
return false unless @transcript.include?(CH)
|
314
|
+
return false if @endpoint == :client && @transcript.include?(SF)
|
315
|
+
return false if @endpoint == :server && @transcript.include?(CF)
|
316
|
+
|
317
|
+
true
|
318
|
+
end
|
319
|
+
|
320
|
+
# @param symbol [Symbol] key of ALERT_DESCRIPTION
|
321
|
+
#
|
322
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
323
|
+
def terminate(symbol)
|
324
|
+
send_alert(symbol)
|
325
|
+
raise Error::ErrorAlerts, symbol
|
326
|
+
end
|
327
|
+
|
328
|
+
def handle_received_alert(alert)
|
329
|
+
unless alert.description == Message::ALERT_DESCRIPTION[:close_notify] ||
|
330
|
+
alert.description == Message::ALERT_DESCRIPTION[:user_canceled]
|
331
|
+
raise alert.to_error
|
332
|
+
end
|
333
|
+
|
334
|
+
@state = EOF
|
335
|
+
end
|
336
|
+
|
337
|
+
# @param _nst [TTTLS13::Message::NewSessionTicket]
|
338
|
+
#
|
339
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
340
|
+
def process_new_session_ticket(_nst)
|
341
|
+
terminate(:unexpected_message) if @endpoint == :server
|
342
|
+
end
|
343
|
+
|
344
|
+
# @param certificate_list [Array of CertificateEntry]
|
345
|
+
# @param ca_file [String] path to ca.crt
|
346
|
+
# @param hostname [String]
|
347
|
+
#
|
348
|
+
# @return [Boolean]
|
349
|
+
# rubocop: disable Metrics/AbcSize
|
350
|
+
def certified_certificate?(certificate_list, ca_file = nil, hostname = nil)
|
351
|
+
store = OpenSSL::X509::Store.new
|
352
|
+
store.set_default_paths
|
353
|
+
store.add_file(ca_file) unless ca_file.nil?
|
354
|
+
|
355
|
+
cert_bin = certificate_list.first.cert_data
|
356
|
+
cert = OpenSSL::X509::Certificate.new(cert_bin)
|
357
|
+
|
358
|
+
chain = certificate_list[1..].map(&:cert_data).map do |c|
|
359
|
+
OpenSSL::X509::Certificate.new(c)
|
360
|
+
end
|
361
|
+
# TODO: parse authorityInfoAccess::CA Issuers
|
362
|
+
|
363
|
+
ctx = OpenSSL::X509::StoreContext.new(store, cert, chain)
|
364
|
+
|
365
|
+
# not support CN matching, only support SAN matching
|
366
|
+
unless hostname.nil?
|
367
|
+
san = cert.extensions.find { |ex| ex.oid == 'subjectAltName' }
|
368
|
+
terminate(:bad_certificate) if san.nil?
|
369
|
+
ostr = OpenSSL::ASN1.decode(san.to_der).value.last
|
370
|
+
san_match = OpenSSL::ASN1.decode(ostr.value).map(&:value)
|
371
|
+
.map { |s| s.gsub('.', '\.').gsub('*', '.*') }
|
372
|
+
.any? { |s| hostname.match(/#{s}/) }
|
373
|
+
return san_match && ctx.verify
|
374
|
+
end
|
375
|
+
ctx.verify
|
376
|
+
end
|
377
|
+
# rubocop: enable Metrics/AbcSize
|
378
|
+
end
|
379
|
+
# rubocop: enable Metrics/ClassLength
|
380
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Cryptograph
|
7
|
+
class Aead
|
8
|
+
# @param cipher_suite [TTTLS13::CipherSuite]
|
9
|
+
# @param write_key [String]
|
10
|
+
# @param write_iv [String]
|
11
|
+
# @param sequence_number [String] uint64
|
12
|
+
# @param length_of_padding [Integer]
|
13
|
+
def initialize(cipher_suite:, write_key:, write_iv:,
|
14
|
+
sequence_number:, length_of_padding: 0)
|
15
|
+
@cipher_suite = cipher_suite
|
16
|
+
case cipher_suite
|
17
|
+
when CipherSuite::TLS_AES_128_GCM_SHA256
|
18
|
+
@cipher = OpenSSL::Cipher::AES128.new(:GCM)
|
19
|
+
when CipherSuite::TLS_AES_256_GCM_SHA384
|
20
|
+
@cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
21
|
+
when CipherSuite::TLS_CHACHA20_POLY1305_SHA256
|
22
|
+
@cipher = OpenSSL::Cipher.new('chacha20-poly1305')
|
23
|
+
else
|
24
|
+
# Note:
|
25
|
+
# not supported
|
26
|
+
# CipherSuite::TLS_AES_128_CCM_SHA256
|
27
|
+
# CipherSuite::TLS_AES_128_CCM_8_SHA256
|
28
|
+
raise Error::ErrorAlerts, :internal_error
|
29
|
+
end
|
30
|
+
@write_key = write_key
|
31
|
+
@write_iv = write_iv
|
32
|
+
@sequence_number = sequence_number
|
33
|
+
@length_of_padding = length_of_padding
|
34
|
+
end
|
35
|
+
|
36
|
+
# AEAD-Encrypt(write_key, nonce, additional_data, plaintext)
|
37
|
+
#
|
38
|
+
# @param content [String]
|
39
|
+
# @param type [TTTLS13::Message::ContentType]
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
def encrypt(content, type)
|
43
|
+
reset_cipher
|
44
|
+
cipher = @cipher.encrypt
|
45
|
+
plaintext = content + type + "\x00" * @length_of_padding
|
46
|
+
cipher.auth_data = additional_data(plaintext.length)
|
47
|
+
encrypted_data = cipher.update(plaintext) + cipher.final
|
48
|
+
@sequence_number.succ
|
49
|
+
|
50
|
+
encrypted_data + cipher.auth_tag
|
51
|
+
end
|
52
|
+
|
53
|
+
# AEAD-Decrypt(peer_write_key, nonce,
|
54
|
+
# additional_data, AEADEncrypted)
|
55
|
+
#
|
56
|
+
# @param encrypted_record [String]
|
57
|
+
# @param auth_data [String]
|
58
|
+
#
|
59
|
+
# @raise [OpenSSL::Cipher::CipherError]
|
60
|
+
#
|
61
|
+
# @return [String and TTTLS13::Message::ContentType]
|
62
|
+
def decrypt(encrypted_record, auth_data)
|
63
|
+
reset_cipher
|
64
|
+
decipher = @cipher.decrypt
|
65
|
+
auth_tag = encrypted_record[-16..-1]
|
66
|
+
decipher.auth_tag = auth_tag
|
67
|
+
decipher.auth_data = auth_data # record header of TLSCiphertext
|
68
|
+
clear = decipher.update(encrypted_record[0...-16]) # auth_tag
|
69
|
+
decipher.final
|
70
|
+
zeros_len = scan_zeros(clear)
|
71
|
+
postfix_len = 1 + zeros_len # type || zeros
|
72
|
+
@sequence_number.succ
|
73
|
+
|
74
|
+
[clear[0...-postfix_len], clear[-postfix_len]]
|
75
|
+
end
|
76
|
+
|
77
|
+
# NOTE:
|
78
|
+
# struct {
|
79
|
+
# opaque content[TLSPlaintext.length];
|
80
|
+
# ContentType type;
|
81
|
+
# uint8 zeros[length_of_padding];
|
82
|
+
# } TLSInnerPlaintext;
|
83
|
+
#
|
84
|
+
# @param record_size_limit [Integer]
|
85
|
+
#
|
86
|
+
# @return [Integer]
|
87
|
+
def tlsplaintext_length_limit(record_size_limit)
|
88
|
+
record_size_limit - 1 - @length_of_padding
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# @return [String]
|
94
|
+
def additional_data(plaintext_len)
|
95
|
+
ciphertext_len = plaintext_len + 16 # length of auth_tag is 16
|
96
|
+
Message::ContentType::APPLICATION_DATA \
|
97
|
+
+ Message::ProtocolVersion::TLS_1_2 \
|
98
|
+
+ ciphertext_len.to_uint16
|
99
|
+
end
|
100
|
+
|
101
|
+
def reset_cipher
|
102
|
+
@cipher.reset
|
103
|
+
@cipher.key = @write_key
|
104
|
+
iv_len = CipherSuite.iv_len(@cipher_suite)
|
105
|
+
@cipher.iv = @sequence_number.xor(@write_iv, iv_len)
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [String]
|
109
|
+
#
|
110
|
+
# @return [Integer]
|
111
|
+
def scan_zeros(clear)
|
112
|
+
i = 0
|
113
|
+
i += 1 while clear[-i] == "\x00"
|
114
|
+
i
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
module Cryptograph
|
6
|
+
class Passer
|
7
|
+
# @param content [String]
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
def encrypt(content, _type)
|
11
|
+
content
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param encrypted_record [String]
|
15
|
+
#
|
16
|
+
# @return [String and TTTLS13::Message::ContentType]
|
17
|
+
def decrypt(encrypted_record, _auth_data)
|
18
|
+
[encrypted_record, encrypted_record[0]]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
module Error
|
6
|
+
# Generic error, common for all classes under TTTLS13::Error module.
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
# Raised if configure is invalid.
|
10
|
+
class ConfigError < Error; end
|
11
|
+
|
12
|
+
# Raised on received Error Alerts message or invalid message.
|
13
|
+
# https://tools.ietf.org/html/rfc8446#section-6.2
|
14
|
+
# Terminated the connection, so you *cannot* recover from this exception.
|
15
|
+
class ErrorAlerts < Error
|
16
|
+
# @return [TTTLS13::Message::Alert]
|
17
|
+
def to_alert
|
18
|
+
Message::Alert.new(description: ALERT_DESCRIPTION[message.to_sym])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|