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,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
|