raioquic 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/.containerignore +4 -0
- data/.rubocop.yml +93 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Containerfile +6 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +113 -0
- data/LICENSE +28 -0
- data/README.md +48 -0
- data/Rakefile +16 -0
- data/Steepfile +8 -0
- data/example/curlcatcher.rb +18 -0
- data/example/interoperability/README.md +9 -0
- data/example/interoperability/aioquic/aioquic_client.py +47 -0
- data/example/interoperability/aioquic/aioquic_server.py +34 -0
- data/example/interoperability/key.pem +28 -0
- data/example/interoperability/localhost-unasuke-dev.crt +21 -0
- data/example/interoperability/quic-go/sample_server.go +61 -0
- data/example/interoperability/raioquic_client.rb +42 -0
- data/example/interoperability/raioquic_server.rb +43 -0
- data/example/parse_curl_example.rb +108 -0
- data/lib/raioquic/buffer.rb +202 -0
- data/lib/raioquic/core_ext.rb +54 -0
- data/lib/raioquic/crypto/README.md +5 -0
- data/lib/raioquic/crypto/aesgcm.rb +52 -0
- data/lib/raioquic/crypto/backend/aead.rb +52 -0
- data/lib/raioquic/crypto/backend.rb +12 -0
- data/lib/raioquic/crypto.rb +10 -0
- data/lib/raioquic/quic/configuration.rb +81 -0
- data/lib/raioquic/quic/connection.rb +2776 -0
- data/lib/raioquic/quic/crypto.rb +317 -0
- data/lib/raioquic/quic/event.rb +69 -0
- data/lib/raioquic/quic/logger.rb +272 -0
- data/lib/raioquic/quic/packet.rb +471 -0
- data/lib/raioquic/quic/packet_builder.rb +301 -0
- data/lib/raioquic/quic/rangeset.rb +113 -0
- data/lib/raioquic/quic/recovery.rb +528 -0
- data/lib/raioquic/quic/stream.rb +343 -0
- data/lib/raioquic/quic.rb +20 -0
- data/lib/raioquic/tls.rb +1659 -0
- data/lib/raioquic/version.rb +5 -0
- data/lib/raioquic.rb +12 -0
- data/misc/export_x25519.py +43 -0
- data/misc/gen_rfc8448_keypair.rb +90 -0
- data/raioquic.gemspec +37 -0
- data/sig/raioquic/buffer.rbs +37 -0
- data/sig/raioquic/core_ext.rbs +7 -0
- data/sig/raioquic/crypto/aesgcm.rbs +20 -0
- data/sig/raioquic/crypto/backend/aead.rbs +11 -0
- data/sig/raioquic/quic/configuration.rbs +34 -0
- data/sig/raioquic/quic/connection.rbs +277 -0
- data/sig/raioquic/quic/crypto.rbs +88 -0
- data/sig/raioquic/quic/event.rbs +51 -0
- data/sig/raioquic/quic/logger.rbs +57 -0
- data/sig/raioquic/quic/packet.rbs +157 -0
- data/sig/raioquic/quic/packet_builder.rbs +76 -0
- data/sig/raioquic/quic/rangeset.rbs +17 -0
- data/sig/raioquic/quic/recovery.rbs +142 -0
- data/sig/raioquic/quic/stream.rbs +87 -0
- data/sig/raioquic/tls.rbs +444 -0
- data/sig/raioquic.rbs +9 -0
- metadata +121 -0
data/lib/raioquic/tls.rb
ADDED
@@ -0,0 +1,1659 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "certifi"
|
5
|
+
require "tttls1.3"
|
6
|
+
require_relative "core_ext"
|
7
|
+
|
8
|
+
module Raioquic
|
9
|
+
# Raioquic::TLS
|
10
|
+
# Migrated from auiquic/src/aioquic/tls.py
|
11
|
+
module TLS # rubocop:disable Metrics/ModuleLength
|
12
|
+
TLS_VERSION_1_2 = 0x0303
|
13
|
+
TLS_VERSION_1_3 = 0x0304
|
14
|
+
TLS_VERSION_1_3_DRAFT_28 = 0x7f1c
|
15
|
+
TLS_VERSION_1_3_DRAFT_27 = 0x7f1b
|
16
|
+
TLS_VERSION_1_3_DRAFT_26 = 0x7f1a
|
17
|
+
|
18
|
+
class AlertDescription
|
19
|
+
CLOSE_NOTIFY = 0
|
20
|
+
UNEXPECTED_MESSAGE = 10
|
21
|
+
BAD_RECORD_MAC = 20
|
22
|
+
RECORD_OVERFLOW = 22
|
23
|
+
HANDSHAKE_FAILURE = 40
|
24
|
+
BAD_CERTIFICATE = 42
|
25
|
+
UNSUPPORTED_CERTIFICATE = 43
|
26
|
+
CERTIFICATE_REVOKED = 44
|
27
|
+
CERTIFICATE_EXPIRED = 45
|
28
|
+
CERTIFICATE_UNKNOWN = 46
|
29
|
+
ILLEGAL_PARAMETER = 47
|
30
|
+
UNKNOWN_CA = 48
|
31
|
+
ACCESS_DENIED = 49
|
32
|
+
DECODE_ERROR = 50
|
33
|
+
DECRYPT_ERROR = 51
|
34
|
+
PROTOCOL_VERSION = 70
|
35
|
+
INSUFFICIENT_SECURITY = 71
|
36
|
+
INTERNAL_ERROR = 80
|
37
|
+
INAPPROPRIATE_FALLBACK = 86
|
38
|
+
USER_CANCELED = 90
|
39
|
+
MISSING_EXTENSION = 109
|
40
|
+
UNSUPPORTED_EXTENSION = 110
|
41
|
+
UNRECOGNIZED_NAME = 112
|
42
|
+
BAD_CERTIFICATE_STATUS_RESPONSE = 113
|
43
|
+
UNKNOWN_PSK_IDENTITY = 115
|
44
|
+
CERTIFICATE_REQUIRED = 116
|
45
|
+
NO_APPLICATION_PROTOCOL = 120
|
46
|
+
end
|
47
|
+
|
48
|
+
# Abstrust class of tls alert protocol message
|
49
|
+
class Alert < StandardError
|
50
|
+
end
|
51
|
+
|
52
|
+
# Represent TLS alert bad_certificate
|
53
|
+
class AlertBadCertificate < Alert
|
54
|
+
def description
|
55
|
+
AlertDescription::BAD_CERTIFICATE
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Represent TLS alert certificate_expired
|
60
|
+
class AlertCertificateExpired < Alert
|
61
|
+
def description
|
62
|
+
AlertDescription::CERTIFICATE_EXPIRED
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Represent TLS alert decrypt_error
|
67
|
+
class AlertDecryptError < Alert
|
68
|
+
def description
|
69
|
+
AlertDescription::DECRYPT_ERROR
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Represent TLS alert handshake_failure
|
74
|
+
class AlertHandshakeFailure < Alert
|
75
|
+
def description
|
76
|
+
AlertDescription::HANDSHAKE_FAILURE
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Represent TLS alert illegal_parameter
|
81
|
+
class AlertIllegalParameter < Alert
|
82
|
+
def description
|
83
|
+
AlertDescription::ILLEGAL_PARAMETER
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Represent TLS alert internal_error
|
88
|
+
class AlertInternalError < Alert
|
89
|
+
def description
|
90
|
+
AlertDescription::INTERNAL_ERROR
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Represent TLS alert protocol_version
|
95
|
+
class AlertProtocolVersion < Alert
|
96
|
+
def description
|
97
|
+
AlertDescription::PROTOCOL_VERSION
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Represent TLS alert unexpected_message
|
102
|
+
class AlertUnexpectedMessage < Alert
|
103
|
+
def description
|
104
|
+
AlertDescription::UNEXPECTED_MESSAGE
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Direction
|
109
|
+
DECRYPT = 0
|
110
|
+
ENCRYPT = 1
|
111
|
+
end
|
112
|
+
|
113
|
+
class Epoch
|
114
|
+
INITIAL = 0
|
115
|
+
ZERO_RTT = 1
|
116
|
+
HANDSHAKE = 2
|
117
|
+
ONE_RTT = 3
|
118
|
+
end
|
119
|
+
|
120
|
+
class State
|
121
|
+
CLIENT_HANDSHAKE_START = 0
|
122
|
+
CLIENT_EXPECT_SERVER_HELLO = 1
|
123
|
+
CLIENT_EXPECT_ENCRYPTED_EXTENSIONS = 2
|
124
|
+
CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE = 3
|
125
|
+
CLIENT_EXPECT_CERTIFICATE_CERTIFICATE = 4
|
126
|
+
CLIENT_EXPECT_CERTIFICATE_VERIFY = 5
|
127
|
+
CLIENT_EXPECT_FINISHED = 6
|
128
|
+
CLIENT_POST_HANDSHAKE = 7
|
129
|
+
|
130
|
+
SERVER_EXPECT_CLIENT_HELLO = 8
|
131
|
+
SERVER_EXPECT_FINISHED = 9
|
132
|
+
SERVER_POST_HANDSHAKE = 10
|
133
|
+
end
|
134
|
+
|
135
|
+
# Load a PEM-encoded private key
|
136
|
+
def self.load_pem_private_key(data, password = nil)
|
137
|
+
OpenSSL::PKey.read(data, password)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Load a chain of PEM-encoded X509 certificates
|
141
|
+
def self.load_pem_x509_certificates(data)
|
142
|
+
boundary = "-----END CERTIFICATE-----\n"
|
143
|
+
certificates = []
|
144
|
+
data.split(boundary).each do |chunk|
|
145
|
+
certificates << OpenSSL::X509::Certificate.new(chunk + boundary)
|
146
|
+
end
|
147
|
+
return certificates
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.load_der_x509_certificate(certificate)
|
151
|
+
OpenSSL::X509::Certificate.new(certificate)
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.verify_certificate(certificate:, chain: [], server_name: nil, cadata: nil, cafile: nil, capath: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
155
|
+
now = Time.now
|
156
|
+
raise AlertCertificateExpired, "Certificate is not valid yet" if now < certificate.not_before
|
157
|
+
raise AlertCertificateExpired, "Certificate is no longer valid" if now > certificate.not_after
|
158
|
+
|
159
|
+
if server_name && !OpenSSL::SSL.verify_certificate_identity(certificate, server_name)
|
160
|
+
raise AlertBadCertificate, "hostname '#{server_name}' doesn't match '#{certificate.subject.to_a.find { |a| a[0] == "CN" }[1]}'"
|
161
|
+
end
|
162
|
+
|
163
|
+
store = OpenSSL::X509::Store.new
|
164
|
+
store.add_file(Certifi.where)
|
165
|
+
|
166
|
+
load_pem_x509_certificates(cadata).each { |cert| store.add_cert(cert) } if cadata
|
167
|
+
store.add_file(cafile) if cafile
|
168
|
+
store.add_path(capath) if capath
|
169
|
+
|
170
|
+
ctx = OpenSSL::X509::StoreContext.new(store, certificate, chain.compact)
|
171
|
+
raise AlertBadCertificate, ctx.error_string unless ctx.verify
|
172
|
+
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
class CipherSuite
|
177
|
+
AES_128_GCM_SHA256 = 0x1301
|
178
|
+
AES_256_GCM_SHA384 = 0x1302
|
179
|
+
CHACHA20_POLY1305_SHA256 = 0x1303
|
180
|
+
EMPTY_RENEGOTIATION_INFO_SCSV = 0x00ff
|
181
|
+
end
|
182
|
+
|
183
|
+
class CompressionMethod
|
184
|
+
NULL = 0
|
185
|
+
end
|
186
|
+
|
187
|
+
class ExtensionType
|
188
|
+
SERVER_NAME = 0
|
189
|
+
STATUS_REQUEST = 5
|
190
|
+
SUPPORTED_GROUPS = 10
|
191
|
+
SIGNATURE_ALGORITHMS = 13
|
192
|
+
ALPN = 16
|
193
|
+
COMPRESS_CERTIFICATE = 27
|
194
|
+
PRE_SHARED_KEY = 41
|
195
|
+
EARLY_DATA = 42
|
196
|
+
SUPPORTED_VERSIONS = 43
|
197
|
+
COOKIE = 44
|
198
|
+
PSK_KEY_EXCHANGE_MODES = 45
|
199
|
+
KEY_SHARE = 51
|
200
|
+
QUIC_TRANSPORT_PARAMETERS = 0x0039
|
201
|
+
QUIC_TRANSPORT_PARAMETERS_DRAFT = 0xffa5
|
202
|
+
ENCRYPTED_SERVER_NAME = 65486
|
203
|
+
end
|
204
|
+
|
205
|
+
class Group
|
206
|
+
SECP256R1 = 0x0017
|
207
|
+
SECP384R1 = 0x0018
|
208
|
+
SECP521R1 = 0x0019
|
209
|
+
X25519 = 0x001d
|
210
|
+
X448 = 0x001e
|
211
|
+
GREASE = 0xaaaa
|
212
|
+
end
|
213
|
+
|
214
|
+
class HandshakeType
|
215
|
+
CLIENT_HELLO = 1
|
216
|
+
SERVER_HELLO = 2
|
217
|
+
NEW_SESSION_TICKET = 4
|
218
|
+
END_OF_EARLY_DATA = 5
|
219
|
+
ENCRYPTED_EXTENSIONS = 8
|
220
|
+
CERTIFICATE = 11
|
221
|
+
CERTIFICATE_REQUEST = 13
|
222
|
+
CERTIFICATE_VERIFY = 15
|
223
|
+
FINISHED = 20
|
224
|
+
KEY_UPDATE = 24
|
225
|
+
COMPRESSED_CERTIFICATE = 25
|
226
|
+
MESSAGE_HASH = 254
|
227
|
+
end
|
228
|
+
|
229
|
+
class PskKeyExchangeMode
|
230
|
+
PSK_KE = 0
|
231
|
+
PSK_DHE_KE = 1
|
232
|
+
end
|
233
|
+
|
234
|
+
class SignatureAlgorithm
|
235
|
+
ECDSA_SECP256R1_SHA256 = 0x0403
|
236
|
+
ECDSA_SECP384R1_SHA384 = 0x0503
|
237
|
+
ECDSA_SECP521R1_SHA512 = 0x0603
|
238
|
+
ED25519 = 0x0807 # unsupported in current ruby?
|
239
|
+
ED448 = 0x0808 # unsupported in current ruby?
|
240
|
+
RSA_PKCS1_SHA256 = 0x0401
|
241
|
+
RSA_PKCS1_SHA384 = 0x0501
|
242
|
+
RSA_PKCS1_SHA512 = 0x0601
|
243
|
+
RSA_PSS_PSS_SHA256 = 0x0809
|
244
|
+
RSA_PSS_PSS_SHA384 = 0x080a
|
245
|
+
RSA_PSS_PSS_SHA512 = 0x080b
|
246
|
+
RSA_PSS_RSAE_SHA256 = 0x0804
|
247
|
+
RSA_PSS_RSAE_SHA384 = 0x0805
|
248
|
+
RSA_PSS_RSAE_SHA512 = 0x0806
|
249
|
+
|
250
|
+
# legacy
|
251
|
+
RSA_PKCS1_SHA1 = 0x0201 # unsupported in current ruby?
|
252
|
+
SHA1_DSA = 0x0202 # unsupported in current ruby?
|
253
|
+
ECDSA_SHA1 = 0x0203 # unsupported in current ruby?
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.pull_block(buf:, capacity:)
|
257
|
+
bytes = buf.pull_bytes(capacity)
|
258
|
+
length = bytes.bytes_to_int
|
259
|
+
ends = buf.tell + length
|
260
|
+
yield length
|
261
|
+
raise RuntimeError unless buf.tell == ends
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.push_block(buf:, capacity:)
|
265
|
+
start = buf.tell + capacity
|
266
|
+
buf.seek(start)
|
267
|
+
yield
|
268
|
+
ends = buf.tell
|
269
|
+
length = ends - start
|
270
|
+
buf.seek(start - capacity)
|
271
|
+
buf.push_bytes(length.to_bytes(capacity))
|
272
|
+
buf.seek(ends)
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.pull_list(buf:, capacity:, func:)
|
276
|
+
items = []
|
277
|
+
pull_block(buf: buf, capacity: capacity) do |length|
|
278
|
+
ends = buf.tell + length
|
279
|
+
while buf.tell < ends # rubocop:disable Style/WhileUntilModifier
|
280
|
+
items.append(func.call)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
items
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.push_list(buf:, capacity:, func:, values:)
|
287
|
+
push_block(buf: buf, capacity: capacity) do
|
288
|
+
values.each { |value| func.call(value) }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Pull an opaque value prefixed by a length
|
293
|
+
def self.pull_opaque(buf:, capacity:)
|
294
|
+
bytes = ""
|
295
|
+
pull_block(buf: buf, capacity: capacity) do |length|
|
296
|
+
bytes = buf.pull_bytes(length)
|
297
|
+
end
|
298
|
+
return bytes
|
299
|
+
end
|
300
|
+
|
301
|
+
# Push an opaque value prefix by a length.
|
302
|
+
def self.push_opaque(buf:, capacity:, value:)
|
303
|
+
push_block(buf: buf, capacity: capacity) do
|
304
|
+
buf.push_bytes(value)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.push_extension(buf:, extension_type:)
|
309
|
+
buf.push_uint16(extension_type)
|
310
|
+
push_block(buf: buf, capacity: 2) do
|
311
|
+
yield if block_given?
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.pull_key_share(buf:)
|
316
|
+
group = buf.pull_uint16
|
317
|
+
data = pull_opaque(buf: buf, capacity: 2)
|
318
|
+
return [group, data]
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.push_key_share(buf:, value:)
|
322
|
+
buf.push_uint16(value[0])
|
323
|
+
push_opaque(buf: buf, capacity: 2, value: value[1])
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.pull_alpn_protocol(buf:)
|
327
|
+
return pull_opaque(buf: buf, capacity: 1)
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.push_alpn_protocol(buf:, protocol:)
|
331
|
+
push_opaque(buf: buf, capacity: 1, value: protocol)
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.pull_psk_identity(buf:)
|
335
|
+
identity = pull_opaque(buf: buf, capacity: 2)
|
336
|
+
obfuscated_ticket_age = buf.pull_uint32
|
337
|
+
return [identity, obfuscated_ticket_age]
|
338
|
+
end
|
339
|
+
|
340
|
+
def self.push_psk_identity(buf:, entry:)
|
341
|
+
push_opaque(buf: buf, capacity: 2, value: entry[0])
|
342
|
+
buf.push_uint32(entry[1])
|
343
|
+
end
|
344
|
+
|
345
|
+
def self.pull_psk_binder(buf:)
|
346
|
+
pull_opaque(buf: buf, capacity: 1)
|
347
|
+
end
|
348
|
+
|
349
|
+
def self.push_psk_binder(buf:, binder:)
|
350
|
+
push_opaque(buf: buf, capacity: 1, value: binder)
|
351
|
+
end
|
352
|
+
|
353
|
+
OfferedPsks = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
354
|
+
:identities,
|
355
|
+
:binders,
|
356
|
+
)
|
357
|
+
|
358
|
+
ClientHello = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
359
|
+
:random,
|
360
|
+
:legacy_session_id,
|
361
|
+
:cipher_suites,
|
362
|
+
:legacy_compression_methods,
|
363
|
+
:alpn_protocols,
|
364
|
+
:early_data,
|
365
|
+
:key_share,
|
366
|
+
:pre_shared_key,
|
367
|
+
:psk_key_exchange_modes,
|
368
|
+
:server_name,
|
369
|
+
:signature_algorithms,
|
370
|
+
:supported_groups,
|
371
|
+
:supported_versions,
|
372
|
+
:other_extensions,
|
373
|
+
)
|
374
|
+
|
375
|
+
def self.pull_client_hello(buf) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
376
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::CLIENT_HELLO
|
377
|
+
|
378
|
+
hello = ClientHello.new
|
379
|
+
pull_block(buf: buf, capacity: 3) do # rubocop:disable Metrics/BlockLength
|
380
|
+
raise RuntimeError unless buf.pull_uint16 == TLS_VERSION_1_2
|
381
|
+
|
382
|
+
hello.random = buf.pull_bytes(32)
|
383
|
+
hello.legacy_session_id = pull_opaque(buf: buf, capacity: 1)
|
384
|
+
hello.cipher_suites = pull_list(buf: buf, capacity: 2, func: buf.method(:pull_uint16))
|
385
|
+
hello.legacy_compression_methods = pull_list(buf: buf, capacity: 1, func: buf.method(:pull_uint8))
|
386
|
+
hello.early_data = false
|
387
|
+
hello.other_extensions = []
|
388
|
+
|
389
|
+
after_psk = false
|
390
|
+
pull_extension = lambda do # rubocop:disable Metrics/BlockLength
|
391
|
+
raise RuntimeError if after_psk
|
392
|
+
|
393
|
+
extension_type = buf.pull_uint16
|
394
|
+
extension_length = buf.pull_uint16
|
395
|
+
case extension_type
|
396
|
+
when ExtensionType::KEY_SHARE
|
397
|
+
hello.key_share = pull_list(buf: buf, capacity: 2, func: -> { pull_key_share(buf: buf) })
|
398
|
+
when ExtensionType::SUPPORTED_VERSIONS
|
399
|
+
hello.supported_versions = pull_list(buf: buf, capacity: 1, func: buf.method(:pull_uint16))
|
400
|
+
when ExtensionType::SIGNATURE_ALGORITHMS
|
401
|
+
hello.signature_algorithms = pull_list(buf: buf, capacity: 2, func: buf.method(:pull_uint16))
|
402
|
+
when ExtensionType::SUPPORTED_GROUPS
|
403
|
+
hello.supported_groups = pull_list(buf: buf, capacity: 2, func: buf.method(:pull_uint16))
|
404
|
+
when ExtensionType::PSK_KEY_EXCHANGE_MODES
|
405
|
+
hello.psk_key_exchange_modes = pull_list(buf: buf, capacity: 1, func: buf.method(:pull_uint8))
|
406
|
+
when ExtensionType::SERVER_NAME
|
407
|
+
pull_block(buf: buf, capacity: 2) do
|
408
|
+
raise RuntimeError unless buf.pull_uint8 == 0
|
409
|
+
|
410
|
+
hello.server_name = pull_opaque(buf: buf, capacity: 2)
|
411
|
+
end
|
412
|
+
when ExtensionType::ALPN
|
413
|
+
hello.alpn_protocols = pull_list(buf: buf, capacity: 2, func: -> { pull_alpn_protocol(buf: buf) })
|
414
|
+
when ExtensionType::EARLY_DATA
|
415
|
+
hello.early_data = true
|
416
|
+
when ExtensionType::PRE_SHARED_KEY
|
417
|
+
hello.pre_shared_key = OfferedPsks.new.tap do |op|
|
418
|
+
op.identities = pull_list(buf: buf, capacity: 2, func: -> { pull_psk_identity(buf: buf) })
|
419
|
+
op.binders = pull_list(buf: buf, capacity: 2, func: -> { pull_psk_binder(buf: buf) })
|
420
|
+
end
|
421
|
+
after_psk = true
|
422
|
+
else
|
423
|
+
hello.other_extensions << [extension_type, buf.pull_bytes(extension_length)]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
pull_list(buf: buf, capacity: 2, func: pull_extension)
|
427
|
+
end
|
428
|
+
|
429
|
+
return hello
|
430
|
+
end
|
431
|
+
|
432
|
+
# rubocop:disable Metrics/BlockLength
|
433
|
+
def self.push_client_hello(buf:, hello:) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
434
|
+
buf.push_uint8(HandshakeType::CLIENT_HELLO)
|
435
|
+
push_block(buf: buf, capacity: 3) do
|
436
|
+
buf.push_uint16(TLS_VERSION_1_2)
|
437
|
+
buf.push_bytes(hello.random)
|
438
|
+
push_opaque(buf: buf, capacity: 1, value: hello.legacy_session_id)
|
439
|
+
push_list(buf: buf, capacity: 2, func: buf.method(:push_uint16), values: hello.cipher_suites)
|
440
|
+
push_list(buf: buf, capacity: 1, func: buf.method(:push_uint8), values: hello.legacy_compression_methods)
|
441
|
+
|
442
|
+
# extensions
|
443
|
+
push_block(buf: buf, capacity: 2) do
|
444
|
+
push_extension(buf: buf, extension_type: ExtensionType::KEY_SHARE) do
|
445
|
+
push_list(buf: buf, capacity: 2, func: ->(val) { push_key_share(buf: buf, value: val) }, values: hello.key_share)
|
446
|
+
end
|
447
|
+
|
448
|
+
push_extension(buf: buf, extension_type: ExtensionType::SUPPORTED_VERSIONS) do
|
449
|
+
push_list(buf: buf, capacity: 1, func: buf.method(:push_uint16), values: hello.supported_versions)
|
450
|
+
end
|
451
|
+
|
452
|
+
push_extension(buf: buf, extension_type: ExtensionType::SIGNATURE_ALGORITHMS) do
|
453
|
+
push_list(buf: buf, capacity: 2, func: buf.method(:push_uint16), values: hello.signature_algorithms)
|
454
|
+
end
|
455
|
+
|
456
|
+
push_extension(buf: buf, extension_type: ExtensionType::SUPPORTED_GROUPS) do
|
457
|
+
push_list(buf: buf, capacity: 2, func: buf.method(:push_uint16), values: hello.supported_groups)
|
458
|
+
end
|
459
|
+
|
460
|
+
if hello.psk_key_exchange_modes
|
461
|
+
push_extension(buf: buf, extension_type: ExtensionType::PSK_KEY_EXCHANGE_MODES) do
|
462
|
+
push_list(buf: buf, capacity: 1, func: buf.method(:push_uint8), values: hello.psk_key_exchange_modes)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
if hello.server_name
|
467
|
+
push_extension(buf: buf, extension_type: ExtensionType::SERVER_NAME) do
|
468
|
+
push_block(buf: buf, capacity: 2) do
|
469
|
+
buf.push_uint8(0)
|
470
|
+
push_opaque(buf: buf, capacity: 2, value: hello.server_name)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
if hello.alpn_protocols && hello.alpn_protocols.length > 0
|
476
|
+
push_extension(buf: buf, extension_type: ExtensionType::ALPN) do
|
477
|
+
push_list(buf: buf, capacity: 2, func: ->(proto) { push_alpn_protocol(buf: buf, protocol: proto) }, values: hello.alpn_protocols)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
hello.other_extensions.each do |extension|
|
482
|
+
push_extension(buf: buf, extension_type: extension[0]) do
|
483
|
+
buf.push_bytes(extension[1])
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
if hello.early_data
|
488
|
+
push_extension(buf: buf, extension_type: ExtensionType::EARLY_DATA) do
|
489
|
+
# pass
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
if hello.pre_shared_key
|
494
|
+
push_extension(buf: buf, extension_type: ExtensionType::PRE_SHARED_KEY) do
|
495
|
+
push_list(buf: buf, capacity: 2, func: ->(val) { push_psk_identity(buf: buf, entry: val) }, values: hello.pre_shared_key.identities)
|
496
|
+
push_list(buf: buf, capacity: 2, func: ->(val) { push_psk_binder(buf: buf, binder: val) }, values: hello.pre_shared_key.binders)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
# rubocop:enable Metrics/BlockLength
|
503
|
+
|
504
|
+
ServerHello = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
505
|
+
:random,
|
506
|
+
:legacy_session_id,
|
507
|
+
:cipher_suite,
|
508
|
+
:compression_method,
|
509
|
+
:key_share,
|
510
|
+
:pre_shared_key,
|
511
|
+
:supported_version,
|
512
|
+
:other_extensions,
|
513
|
+
)
|
514
|
+
|
515
|
+
def self.pull_server_hello(buf)
|
516
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::SERVER_HELLO
|
517
|
+
|
518
|
+
hello = ServerHello.new
|
519
|
+
pull_block(buf: buf, capacity: 3) do
|
520
|
+
raise RuntimeError unless buf.pull_uint16 == TLS_VERSION_1_2
|
521
|
+
|
522
|
+
hello.random = buf.pull_bytes(32)
|
523
|
+
hello.legacy_session_id = pull_opaque(buf: buf, capacity: 1)
|
524
|
+
hello.cipher_suite = buf.pull_uint16
|
525
|
+
hello.compression_method = buf.pull_uint8
|
526
|
+
hello.other_extensions = []
|
527
|
+
|
528
|
+
pull_extension = lambda do
|
529
|
+
extension_type = buf.pull_uint16
|
530
|
+
extension_length = buf.pull_uint16
|
531
|
+
case extension_type
|
532
|
+
when ExtensionType::SUPPORTED_VERSIONS
|
533
|
+
hello.supported_version = buf.pull_uint16
|
534
|
+
when ExtensionType::KEY_SHARE
|
535
|
+
hello.key_share = pull_key_share(buf: buf)
|
536
|
+
when ExtensionType::PRE_SHARED_KEY
|
537
|
+
hello.pre_shared_key = buf.pull_uint16
|
538
|
+
else
|
539
|
+
hello.other_extensions << [extension_type, buf.pull_bytes(extension_length)]
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
pull_list(buf: buf, capacity: 2, func: pull_extension)
|
544
|
+
end
|
545
|
+
|
546
|
+
return hello
|
547
|
+
end
|
548
|
+
|
549
|
+
def self.push_server_hello(buf:, hello:) # rubocop:disable Metrics/MethodLength
|
550
|
+
hello.compression_method ||= CompressionMethod::NULL
|
551
|
+
hello.other_extensions ||= []
|
552
|
+
|
553
|
+
buf.push_uint8(HandshakeType::SERVER_HELLO)
|
554
|
+
push_block(buf: buf, capacity: 3) do # rubocop:disable Metrics/BlockLength
|
555
|
+
buf.push_uint16(TLS_VERSION_1_2)
|
556
|
+
buf.push_bytes(hello.random)
|
557
|
+
|
558
|
+
push_opaque(buf: buf, capacity: 1, value: hello.legacy_session_id)
|
559
|
+
buf.push_uint16(hello.cipher_suite)
|
560
|
+
buf.push_uint8(hello.compression_method)
|
561
|
+
|
562
|
+
# extensions
|
563
|
+
push_block(buf: buf, capacity: 2) do
|
564
|
+
if hello.supported_version
|
565
|
+
push_extension(buf: buf, extension_type: ExtensionType::SUPPORTED_VERSIONS) do
|
566
|
+
buf.push_uint16(hello.supported_version)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
if hello.key_share
|
571
|
+
push_extension(buf: buf, extension_type: ExtensionType::KEY_SHARE) do
|
572
|
+
push_key_share(buf: buf, value: hello.key_share)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
if hello.pre_shared_key
|
577
|
+
push_extension(buf: buf, extension_type: ExtensionType::PRE_SHARED_KEY) do
|
578
|
+
buf.push_uint16(hello.pre_shared_key)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
hello.other_extensions.each do |extension|
|
583
|
+
push_extension(buf: buf, extension_type: extension[0]) do
|
584
|
+
buf.push_bytes(extension[1])
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
NewSessionTicket = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
592
|
+
:ticket_lifetime,
|
593
|
+
:ticket_age_add,
|
594
|
+
:ticket_nonce,
|
595
|
+
:ticket,
|
596
|
+
:max_early_data_size,
|
597
|
+
:other_extensions,
|
598
|
+
)
|
599
|
+
|
600
|
+
def self.pull_new_session_ticket(buf)
|
601
|
+
new_session_ticket = NewSessionTicket.new
|
602
|
+
new_session_ticket.other_extensions = []
|
603
|
+
|
604
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::NEW_SESSION_TICKET
|
605
|
+
|
606
|
+
pull_block(buf: buf, capacity: 3) do
|
607
|
+
new_session_ticket.ticket_lifetime = buf.pull_uint32
|
608
|
+
new_session_ticket.ticket_age_add = buf.pull_uint32
|
609
|
+
new_session_ticket.ticket_nonce = pull_opaque(buf: buf, capacity: 1)
|
610
|
+
new_session_ticket.ticket = pull_opaque(buf: buf, capacity: 2)
|
611
|
+
|
612
|
+
pull_extension = lambda do
|
613
|
+
extension_type = buf.pull_uint16
|
614
|
+
extension_length = buf.pull_uint16
|
615
|
+
if extension_type == ExtensionType::EARLY_DATA
|
616
|
+
new_session_ticket.max_early_data_size = buf.pull_uint32
|
617
|
+
else
|
618
|
+
new_session_ticket.other_extensions << [extension_type, buf.pull_bytes(extension_length)]
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
pull_list(buf: buf, capacity: 2, func: pull_extension)
|
623
|
+
end
|
624
|
+
|
625
|
+
return new_session_ticket
|
626
|
+
end
|
627
|
+
|
628
|
+
def self.push_new_session_ticket(buf:, new_session_ticket:)
|
629
|
+
buf.push_uint8(HandshakeType::NEW_SESSION_TICKET)
|
630
|
+
push_block(buf: buf, capacity: 3) do
|
631
|
+
buf.push_uint32(new_session_ticket.ticket_lifetime)
|
632
|
+
buf.push_uint32(new_session_ticket.ticket_age_add)
|
633
|
+
push_opaque(buf: buf, capacity: 1, value: new_session_ticket.ticket_nonce)
|
634
|
+
push_opaque(buf: buf, capacity: 2, value: new_session_ticket.ticket)
|
635
|
+
|
636
|
+
push_block(buf: buf, capacity: 2) do
|
637
|
+
if new_session_ticket.max_early_data_size
|
638
|
+
push_extension(buf: buf, extension_type: ExtensionType::EARLY_DATA) do
|
639
|
+
buf.push_uint32(new_session_ticket.max_early_data_size)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
new_session_ticket.other_extensions.each do |extension|
|
644
|
+
push_extension(buf: buf, extension_type: extension[0]) do
|
645
|
+
buf.push_bytes(extension[1])
|
646
|
+
end
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
EncryptedExtensions = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
653
|
+
:alpn_protocol,
|
654
|
+
:early_data,
|
655
|
+
:other_extensions,
|
656
|
+
)
|
657
|
+
|
658
|
+
def self.pull_encrypted_extensions(buf)
|
659
|
+
extensions = EncryptedExtensions.new
|
660
|
+
extensions.other_extensions = []
|
661
|
+
|
662
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::ENCRYPTED_EXTENSIONS
|
663
|
+
|
664
|
+
pull_block(buf: buf, capacity: 3) do
|
665
|
+
pull_extensions = lambda do
|
666
|
+
extension_type = buf.pull_uint16
|
667
|
+
extension_length = buf.pull_uint16
|
668
|
+
|
669
|
+
case extension_type
|
670
|
+
when ExtensionType::ALPN
|
671
|
+
extensions.alpn_protocol = pull_list(buf: buf, capacity: 2, func: -> { pull_alpn_protocol(buf: buf) })[0]
|
672
|
+
when ExtensionType::EARLY_DATA
|
673
|
+
extensions.early_data = true
|
674
|
+
else
|
675
|
+
extensions.other_extensions << [extension_type, buf.pull_bytes(extension_length)]
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
pull_list(buf: buf, capacity: 2, func: pull_extensions)
|
680
|
+
end
|
681
|
+
return extensions
|
682
|
+
end
|
683
|
+
|
684
|
+
def self.push_encrypted_extensions(buf:, extensions:)
|
685
|
+
buf.push_uint8(HandshakeType::ENCRYPTED_EXTENSIONS)
|
686
|
+
push_block(buf: buf, capacity: 3) do
|
687
|
+
push_block(buf: buf, capacity: 2) do
|
688
|
+
if extensions.alpn_protocol
|
689
|
+
push_extension(buf: buf, extension_type: ExtensionType::ALPN) do
|
690
|
+
push_list(buf: buf, capacity: 2, func: ->(val) { push_alpn_protocol(buf: buf, protocol: val) }, values: [extensions.alpn_protocol])
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
if extensions.early_data
|
695
|
+
push_extension(buf: buf, extension_type: ExtensionType::EARLY_DATA) do
|
696
|
+
# pass
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
extensions.other_extensions.each do |extension|
|
701
|
+
push_extension(buf: buf, extension_type: extension[0]) do
|
702
|
+
buf.push_bytes(extension[1])
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
Certificate = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
710
|
+
:request_context,
|
711
|
+
:certificates,
|
712
|
+
)
|
713
|
+
|
714
|
+
def self.pull_certificate(buf)
|
715
|
+
certificate = Certificate.new
|
716
|
+
|
717
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::CERTIFICATE
|
718
|
+
|
719
|
+
pull_block(buf: buf, capacity: 3) do
|
720
|
+
certificate.request_context = pull_opaque(buf: buf, capacity: 1)
|
721
|
+
|
722
|
+
pull_certificate_entry = lambda do
|
723
|
+
data = pull_opaque(buf: buf, capacity: 3)
|
724
|
+
extensions = pull_opaque(buf: buf, capacity: 2)
|
725
|
+
return [data, extensions]
|
726
|
+
end
|
727
|
+
|
728
|
+
certificate.certificates = pull_list(buf: buf, capacity: 3, func: pull_certificate_entry)
|
729
|
+
end
|
730
|
+
|
731
|
+
return certificate
|
732
|
+
end
|
733
|
+
|
734
|
+
def self.push_certificate(buf:, certificate:)
|
735
|
+
buf.push_uint8(HandshakeType::CERTIFICATE)
|
736
|
+
push_block(buf: buf, capacity: 3) do
|
737
|
+
push_opaque(buf: buf, capacity: 1, value: certificate.request_context)
|
738
|
+
|
739
|
+
push_certificate_entry = lambda do |entry|
|
740
|
+
push_opaque(buf: buf, capacity: 3, value: entry[0])
|
741
|
+
push_opaque(buf: buf, capacity: 2, value: entry[1])
|
742
|
+
end
|
743
|
+
push_list(buf: buf, capacity: 3, func: push_certificate_entry, values: certificate.certificates)
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
CertificateVerify = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
748
|
+
:algorithm,
|
749
|
+
:signature,
|
750
|
+
)
|
751
|
+
|
752
|
+
def self.pull_certificate_verify(buf)
|
753
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::CERTIFICATE_VERIFY
|
754
|
+
|
755
|
+
certificate_verify = CertificateVerify.new
|
756
|
+
pull_block(buf: buf, capacity: 3) do
|
757
|
+
certificate_verify.algorithm = buf.pull_uint16
|
758
|
+
certificate_verify.signature = pull_opaque(buf: buf, capacity: 2)
|
759
|
+
end
|
760
|
+
|
761
|
+
return certificate_verify
|
762
|
+
end
|
763
|
+
|
764
|
+
def self.push_certificate_verify(buf:, verify:)
|
765
|
+
buf.push_uint8(HandshakeType::CERTIFICATE_VERIFY)
|
766
|
+
push_block(buf: buf, capacity: 3) do
|
767
|
+
buf.push_uint16(verify.algorithm)
|
768
|
+
push_opaque(buf: buf, capacity: 2, value: verify.signature)
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
Finished = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
773
|
+
:verify_data,
|
774
|
+
)
|
775
|
+
|
776
|
+
def self.pull_finished(buf)
|
777
|
+
raise RuntimeError unless buf.pull_uint8 == HandshakeType::FINISHED
|
778
|
+
|
779
|
+
return Finished.new.tap do |f|
|
780
|
+
f.verify_data = pull_opaque(buf: buf, capacity: 3)
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
def self.push_finished(buf:, finished:)
|
785
|
+
buf.push_uint8(HandshakeType::FINISHED)
|
786
|
+
push_opaque(buf: buf, capacity: 3, value: finished.verify_data)
|
787
|
+
end
|
788
|
+
|
789
|
+
# TLS KeySchedule class
|
790
|
+
class KeySchedule
|
791
|
+
attr_reader :cipher_suite
|
792
|
+
attr_accessor :generation
|
793
|
+
attr_reader :hash
|
794
|
+
|
795
|
+
def initialize(cipher_suite)
|
796
|
+
@algorithm = TLS.cipher_suite_hash(cipher_suite)
|
797
|
+
@cipher_suite = cipher_suite
|
798
|
+
@generation = 0
|
799
|
+
@hash = @algorithm.new
|
800
|
+
@hash_empty_value = @hash.dup.digest("")
|
801
|
+
@secret = "\x00" * @hash.digest_length
|
802
|
+
end
|
803
|
+
|
804
|
+
def certificate_verify_data(context_string)
|
805
|
+
("\x20" * 64) + context_string + "\x00" + @hash.dup.digest # rubocop:disable Style/StringConcatenation
|
806
|
+
end
|
807
|
+
|
808
|
+
def finished_verify_data(secret)
|
809
|
+
hmac_key = TTTLS13::KeySchedule.hkdf_expand_label(secret, "finished", "", @hash.digest_length, @hash.name)
|
810
|
+
OpenSSL::HMAC.digest(@hash.name, hmac_key, @hash.dup.digest)
|
811
|
+
end
|
812
|
+
|
813
|
+
def derive_secret(label)
|
814
|
+
TTTLS13::KeySchedule.hkdf_expand_label(@secret, label, @hash.dup.digest, @hash.digest_length, @hash.name)
|
815
|
+
end
|
816
|
+
|
817
|
+
def extract(key_material = nil)
|
818
|
+
key_material ||= "\x00" * @hash.digest_length
|
819
|
+
|
820
|
+
if @generation > 0 # rubocop:disable Style/IfUnlessModifier
|
821
|
+
@secret = TTTLS13::KeySchedule.hkdf_expand_label(@secret, "derived", @hash_empty_value, @hash.digest_length, @hash.name)
|
822
|
+
end
|
823
|
+
@generation += 1
|
824
|
+
@secret = OpenSSL::HMAC.digest(@hash.name, @secret, key_material)
|
825
|
+
end
|
826
|
+
|
827
|
+
def update_hash(data)
|
828
|
+
@hash.update(data)
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
# KeyScheduleProxy binds several KeySchedule object that has different digest length
|
833
|
+
class KeyScheduleProxy
|
834
|
+
def initialize(cipher_suites)
|
835
|
+
@schedules = cipher_suites.each_with_object({}) do |cipher_suite, hash|
|
836
|
+
hash[cipher_suite] = KeySchedule.new(cipher_suite)
|
837
|
+
hash
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
def extract(key_material = nil)
|
842
|
+
@schedules.each_value { |schedule| schedule.extract(key_material) }
|
843
|
+
end
|
844
|
+
|
845
|
+
def select(cipher_suite)
|
846
|
+
@schedules[cipher_suite]
|
847
|
+
end
|
848
|
+
|
849
|
+
def update_hash(data)
|
850
|
+
@schedules.each_value { |schedule| schedule.update_hash(data) }
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
CIPHER_SUITES = {
|
855
|
+
CipherSuite::AES_128_GCM_SHA256 => OpenSSL::Digest::SHA256,
|
856
|
+
CipherSuite::AES_256_GCM_SHA384 => OpenSSL::Digest::SHA384,
|
857
|
+
CipherSuite::CHACHA20_POLY1305_SHA256 => OpenSSL::Digest::SHA256,
|
858
|
+
}.freeze
|
859
|
+
|
860
|
+
SIGNATURE_ALGORITHMS = {
|
861
|
+
SignatureAlgorithm::ECDSA_SECP256R1_SHA256 => [nil, OpenSSL::Digest::SHA256],
|
862
|
+
SignatureAlgorithm::ECDSA_SECP384R1_SHA384 => [nil, OpenSSL::Digest::SHA384],
|
863
|
+
SignatureAlgorithm::ECDSA_SECP521R1_SHA512 => [nil, OpenSSL::Digest::SHA512],
|
864
|
+
# SignatureAlgorithm::RSA_PKCS1_SHA1 => [nil, OpenSSL::Digest::SHA1], # TODO: unsupported?
|
865
|
+
SignatureAlgorithm::RSA_PKCS1_SHA256 => [:pss, OpenSSL::Digest::SHA256],
|
866
|
+
SignatureAlgorithm::RSA_PKCS1_SHA384 => [:pss, OpenSSL::Digest::SHA384],
|
867
|
+
SignatureAlgorithm::RSA_PKCS1_SHA512 => [:pss, OpenSSL::Digest::SHA512],
|
868
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA256 => [:pss, OpenSSL::Digest::SHA256],
|
869
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA384 => [:pss, OpenSSL::Digest::SHA384],
|
870
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA512 => [:pss, OpenSSL::Digest::SHA512],
|
871
|
+
}.freeze
|
872
|
+
|
873
|
+
GROUP_TO_CURVE = {
|
874
|
+
Group::SECP256R1 => "prime256v1",
|
875
|
+
Group::SECP384R1 => "secp384r1",
|
876
|
+
Group::SECP521R1 => "secp521r1",
|
877
|
+
}.freeze
|
878
|
+
|
879
|
+
CURVE_TO_GROUP = GROUP_TO_CURVE.invert.freeze
|
880
|
+
|
881
|
+
def self.cipher_suite_hash(cipher_suite)
|
882
|
+
CIPHER_SUITES[cipher_suite]
|
883
|
+
end
|
884
|
+
|
885
|
+
def self.decode_public_key(key_share)
|
886
|
+
case key_share[0]
|
887
|
+
when Group::X25519
|
888
|
+
OpenSSL::PKey.read(key_share[1])
|
889
|
+
when Group::X448
|
890
|
+
raise "X448 did not support yet."
|
891
|
+
else
|
892
|
+
if GROUP_TO_CURVE.key?(key_share[0])
|
893
|
+
group = OpenSSL::PKey::EC::Group.new(GROUP_TO_CURVE[key_share[0]])
|
894
|
+
OpenSSL::PKey::EC::Point.new(group, key_share[1])
|
895
|
+
end
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
# NOTE: X25519, X448 are not supported
|
900
|
+
def self.encode_public_key(public_key)
|
901
|
+
if public_key.respond_to?(:oid) && public_key.oid == "X25519"
|
902
|
+
[Group::X25519, public_key.public_to_der]
|
903
|
+
else
|
904
|
+
[CURVE_TO_GROUP[public_key.group.curve_name], public_key.to_octet_string(:uncompressed)]
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
def self.negotiate(supported:, offered: nil, exc: nil)
|
909
|
+
if offered
|
910
|
+
supported.each do |c|
|
911
|
+
return c if offered.include?(c)
|
912
|
+
end
|
913
|
+
end
|
914
|
+
|
915
|
+
raise exc if exc
|
916
|
+
|
917
|
+
return nil
|
918
|
+
end
|
919
|
+
|
920
|
+
def self.signature_algorithm_params(signature_algorithm)
|
921
|
+
raise NotImplementedError
|
922
|
+
end
|
923
|
+
|
924
|
+
def self.push_message(key_schedule:, buf:)
|
925
|
+
hash_start = buf.tell
|
926
|
+
yield
|
927
|
+
key_schedule.update_hash(buf.data_slice(start: hash_start, ends: buf.tell))
|
928
|
+
end
|
929
|
+
|
930
|
+
SessionTicket = _ = Struct.new( # rubocop:disable Naming/ConstantName
|
931
|
+
:age_add,
|
932
|
+
:cipher_suite,
|
933
|
+
:not_valid_after,
|
934
|
+
:not_valid_before,
|
935
|
+
:resumption_secret,
|
936
|
+
:server_name,
|
937
|
+
:ticket,
|
938
|
+
:max_early_data_size,
|
939
|
+
:other_extensions,
|
940
|
+
) do
|
941
|
+
def is_valid
|
942
|
+
now = Time.now
|
943
|
+
now >= not_valid_before && now <= not_valid_after
|
944
|
+
end
|
945
|
+
|
946
|
+
def obfuscated_age
|
947
|
+
age = (Time.now - not_valid_before) * 1000
|
948
|
+
return (age + age_add) % (1 << 32)
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
# Represent TLS server-side or client-side peer
|
953
|
+
class Context
|
954
|
+
attr_reader :session_resumed
|
955
|
+
attr_reader :enc_key
|
956
|
+
attr_reader :dec_key
|
957
|
+
attr_reader :key_schedule
|
958
|
+
attr_reader :alpn_negotiated
|
959
|
+
attr_reader :received_extensions
|
960
|
+
attr_reader :early_data_accepted
|
961
|
+
|
962
|
+
attr_accessor :state
|
963
|
+
attr_accessor :handshake_extensions
|
964
|
+
attr_accessor :certificate
|
965
|
+
attr_accessor :certificate_chain
|
966
|
+
attr_accessor :certificate_private_key
|
967
|
+
attr_accessor :supported_groups
|
968
|
+
attr_accessor :supported_versions
|
969
|
+
attr_accessor :signature_algorithms
|
970
|
+
attr_accessor :new_session_ticket_cb
|
971
|
+
attr_accessor :get_session_ticket_cb
|
972
|
+
attr_accessor :session_ticket
|
973
|
+
attr_accessor :alpn_cb
|
974
|
+
attr_accessor :update_traffic_key_cb
|
975
|
+
|
976
|
+
def initialize(is_client:, alpn_protocols: [], cadata: nil, cafile: nil, capath: nil, cipher_suites: nil, logger: nil, max_early_data: nil, server_name: nil, verify_mode: nil) # rubocop:disable Layout/LineLength, Metrics/MethodLength
|
977
|
+
@alpn_protocols = alpn_protocols
|
978
|
+
@cadata = cadata
|
979
|
+
@cafile = cafile
|
980
|
+
@capath = capath
|
981
|
+
@certificate = nil
|
982
|
+
@certificate_chain = []
|
983
|
+
@certificate_private_key = nil
|
984
|
+
@handshake_extensions = []
|
985
|
+
@max_early_data = max_early_data
|
986
|
+
@session_ticket = nil
|
987
|
+
@server_name = server_name
|
988
|
+
@verify_mode = if verify_mode
|
989
|
+
verify_mode
|
990
|
+
else
|
991
|
+
is_client ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
992
|
+
end
|
993
|
+
@alpn_cb = nil
|
994
|
+
@get_session_ticket_cb = nil
|
995
|
+
@new_session_ticket_cb = nil
|
996
|
+
@update_traffic_key_cb = ->(direction: _direction, epoch: _epoch, cipher_suite: _cipher_suite, secret: _secret) {}
|
997
|
+
@cipher_suites =
|
998
|
+
cipher_suites || [
|
999
|
+
CipherSuite::AES_256_GCM_SHA384,
|
1000
|
+
CipherSuite::AES_128_GCM_SHA256,
|
1001
|
+
CipherSuite::CHACHA20_POLY1305_SHA256,
|
1002
|
+
]
|
1003
|
+
@legacy_compression_methods = [CompressionMethod::NULL]
|
1004
|
+
@psk_key_exchange_modes = [PskKeyExchangeMode::PSK_DHE_KE]
|
1005
|
+
@signature_algorithms = [
|
1006
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA256,
|
1007
|
+
SignatureAlgorithm::ECDSA_SECP256R1_SHA256,
|
1008
|
+
SignatureAlgorithm::RSA_PKCS1_SHA256,
|
1009
|
+
SignatureAlgorithm::RSA_PKCS1_SHA1,
|
1010
|
+
]
|
1011
|
+
@supported_groups = [Group::SECP256R1]
|
1012
|
+
@supported_versions = [TLS_VERSION_1_3]
|
1013
|
+
|
1014
|
+
# state
|
1015
|
+
@alpn_negotiated = nil
|
1016
|
+
@early_data_accepted = false
|
1017
|
+
@key_schedule = nil
|
1018
|
+
@key_schedule_psk = nil
|
1019
|
+
@received_extensions = nil
|
1020
|
+
@key_schedule_proxy = nil
|
1021
|
+
@new_session_ticket = nil
|
1022
|
+
@peer_certificate = nil
|
1023
|
+
@peer_certificate_chain = []
|
1024
|
+
@receive_buffer = ""
|
1025
|
+
@session_resumed = false
|
1026
|
+
@enc_key = nil
|
1027
|
+
@dec_key = nil
|
1028
|
+
@logger = logger
|
1029
|
+
@ec_key = nil
|
1030
|
+
@ec_private_key = nil
|
1031
|
+
@x25519_private_key = nil
|
1032
|
+
@x448_private_key = nil
|
1033
|
+
|
1034
|
+
if is_client
|
1035
|
+
@client_random = Random.urandom(32)
|
1036
|
+
@legacy_session_id = ""
|
1037
|
+
@state = State::CLIENT_HANDSHAKE_START
|
1038
|
+
else
|
1039
|
+
@client_random = nil
|
1040
|
+
@legacy_session_id = nil
|
1041
|
+
@state = State::SERVER_EXPECT_CLIENT_HELLO
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Style/GuardClause
|
1046
|
+
def handle_message(input_data:, output_buf:)
|
1047
|
+
if @state == State::CLIENT_HANDSHAKE_START
|
1048
|
+
client_send_hello(output_buf[Epoch::INITIAL])
|
1049
|
+
return
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
@receive_buffer += input_data
|
1053
|
+
while @receive_buffer.bytesize >= 4
|
1054
|
+
# determine message length
|
1055
|
+
message_type = @receive_buffer[0].bytes_to_int
|
1056
|
+
message_length = 4 + @receive_buffer[1...4].bytes_to_int
|
1057
|
+
|
1058
|
+
# check message is complete
|
1059
|
+
break if @receive_buffer.bytesize < message_length
|
1060
|
+
|
1061
|
+
message = @receive_buffer[0...message_length]
|
1062
|
+
@receive_buffer = @receive_buffer[message_length..]
|
1063
|
+
|
1064
|
+
input_buf = Buffer.new(data: message)
|
1065
|
+
|
1066
|
+
# client status
|
1067
|
+
case @state
|
1068
|
+
when State::CLIENT_EXPECT_SERVER_HELLO
|
1069
|
+
if message_type == HandshakeType::SERVER_HELLO
|
1070
|
+
client_handle_hello(input_buf: input_buf, output_buf: output_buf[Epoch::INITIAL])
|
1071
|
+
else
|
1072
|
+
raise AlertUnexpectedMessage
|
1073
|
+
end
|
1074
|
+
when State::CLIENT_EXPECT_ENCRYPTED_EXTENSIONS
|
1075
|
+
if message_type == HandshakeType::ENCRYPTED_EXTENSIONS
|
1076
|
+
client_handle_encrypted_extensions(input_buf)
|
1077
|
+
else
|
1078
|
+
raise AlertUnexpectedMessage
|
1079
|
+
end
|
1080
|
+
when State::CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE
|
1081
|
+
if message_type == HandshakeType::CERTIFICATE
|
1082
|
+
client_handle_certificate(input_buf)
|
1083
|
+
else
|
1084
|
+
# FIXME: handle certificate request
|
1085
|
+
raise AlertUnexpectedMessage
|
1086
|
+
end
|
1087
|
+
when State::CLIENT_EXPECT_CERTIFICATE_VERIFY
|
1088
|
+
if message_type == HandshakeType::CERTIFICATE_VERIFY
|
1089
|
+
client_handle_certificate_verify(input_buf)
|
1090
|
+
else
|
1091
|
+
raise AlertUnexpectedMessage
|
1092
|
+
end
|
1093
|
+
when State::CLIENT_EXPECT_FINISHED
|
1094
|
+
if message_type == HandshakeType::FINISHED
|
1095
|
+
client_handle_finished(input_buf: input_buf, output_buf: output_buf[Epoch::HANDSHAKE])
|
1096
|
+
else
|
1097
|
+
raise AlertUnexpectedMessage
|
1098
|
+
end
|
1099
|
+
when State::CLIENT_POST_HANDSHAKE
|
1100
|
+
if message_type == HandshakeType::NEW_SESSION_TICKET
|
1101
|
+
client_handle_new_session_ticket(input_buf)
|
1102
|
+
else
|
1103
|
+
raise AlertUnexpectedMessage
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
# server state
|
1107
|
+
when State::SERVER_EXPECT_CLIENT_HELLO
|
1108
|
+
if message_type == HandshakeType::CLIENT_HELLO
|
1109
|
+
server_handle_hello(
|
1110
|
+
input_buf: input_buf,
|
1111
|
+
initial_buf: output_buf[Epoch::INITIAL],
|
1112
|
+
handshake_buf: output_buf[Epoch::HANDSHAKE],
|
1113
|
+
onertt_buf: output_buf[Epoch::ONE_RTT],
|
1114
|
+
)
|
1115
|
+
else
|
1116
|
+
raise AlertUnexpectedMessage
|
1117
|
+
end
|
1118
|
+
when State::SERVER_EXPECT_FINISHED
|
1119
|
+
if message_type == HandshakeType::FINISHED
|
1120
|
+
server_handle_finished(input_buf: input_buf, output_buf: output_buf[Epoch::ONE_RTT])
|
1121
|
+
else
|
1122
|
+
raise AlertUnexpectedMessage
|
1123
|
+
end
|
1124
|
+
when State::SERVER_POST_HANDSHAKE
|
1125
|
+
raise AlertUnexpectedMessage
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
end
|
1129
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Style/GuardClause
|
1130
|
+
|
1131
|
+
def build_session_ticket(new_session_ticket:, other_extensions:)
|
1132
|
+
resumption_master_secret = @key_schedule&.derive_secret("res master")
|
1133
|
+
resumption_secret = TTTLS13::KeySchedule.hkdf_expand_label(
|
1134
|
+
resumption_master_secret, "resumption", new_session_ticket.ticket_nonce, @key_schedule.hash.digest_length, @key_schedule.hash.name,
|
1135
|
+
)
|
1136
|
+
|
1137
|
+
timestamp = Time.now
|
1138
|
+
return SessionTicket.new.tap do |ticket|
|
1139
|
+
ticket.age_add = new_session_ticket.ticket_age_add
|
1140
|
+
ticket.cipher_suite = @key_schedule.cipher_suite
|
1141
|
+
ticket.max_early_data_size = new_session_ticket.max_early_data_size
|
1142
|
+
ticket.not_valid_after = timestamp + new_session_ticket.ticket_lifetime
|
1143
|
+
ticket.not_valid_before = timestamp
|
1144
|
+
ticket.other_extensions = other_extensions
|
1145
|
+
ticket.resumption_secret = resumption_secret
|
1146
|
+
ticket.server_name = @server_name
|
1147
|
+
ticket.ticket = new_session_ticket.ticket
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
1152
|
+
def client_send_hello(output_buf)
|
1153
|
+
key_share = []
|
1154
|
+
supported_groups = []
|
1155
|
+
|
1156
|
+
@supported_groups.each do |group|
|
1157
|
+
case group
|
1158
|
+
when Group::SECP256R1
|
1159
|
+
ec = OpenSSL::PKey::EC.generate(GROUP_TO_CURVE[Group::SECP256R1])
|
1160
|
+
@ec_key = ec
|
1161
|
+
@ec_private_key = ec.private_key
|
1162
|
+
key_share << TLS.encode_public_key(ec.public_key)
|
1163
|
+
supported_groups << Group::SECP256R1
|
1164
|
+
when Group::X25519, Group::X448
|
1165
|
+
raise "unsupported"
|
1166
|
+
when Group::GREASE
|
1167
|
+
key_share << [Group::GREASE, '\x00']
|
1168
|
+
supported_groups << Group::GREASE
|
1169
|
+
end
|
1170
|
+
end
|
1171
|
+
raise RuntimeError if key_share.size == 0
|
1172
|
+
|
1173
|
+
hello = ClientHello.new.tap do |h|
|
1174
|
+
h.random = @client_random
|
1175
|
+
h.legacy_session_id = @legacy_session_id
|
1176
|
+
h.cipher_suites = @cipher_suites
|
1177
|
+
h.legacy_compression_methods = @legacy_compression_methods
|
1178
|
+
h.alpn_protocols = @alpn_protocols
|
1179
|
+
h.early_data = false
|
1180
|
+
h.key_share = key_share
|
1181
|
+
h.psk_key_exchange_modes = (@session_ticket || @new_session_ticket_cb ? @psk_key_exchange_modes : nil)
|
1182
|
+
h.server_name = @server_name
|
1183
|
+
h.signature_algorithms = @signature_algorithms
|
1184
|
+
h.supported_groups = supported_groups
|
1185
|
+
h.supported_versions = @supported_versions
|
1186
|
+
h.other_extensions = @handshake_extensions
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
# PSK
|
1190
|
+
if @session_ticket&.is_valid
|
1191
|
+
@key_schedule_psk = KeySchedule.new(@session_ticket.cipher_suite)
|
1192
|
+
@key_schedule_psk.extract(@session_ticket.resumption_secret)
|
1193
|
+
binder_key = @key_schedule_psk.derive_secret("res binder")
|
1194
|
+
binder_length = @key_schedule_psk.hash.digest_length
|
1195
|
+
|
1196
|
+
# update hello
|
1197
|
+
hello.early_data = true if @session_ticket.max_early_data_size
|
1198
|
+
hello.pre_shared_key = OfferedPsks.new.tap do |psks|
|
1199
|
+
psks.identities = [[@session_ticket.ticket, @session_ticket.obfuscated_age]]
|
1200
|
+
psks.binders = ["\x00" * binder_length]
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
# serialize hello withouit binder
|
1204
|
+
tmp_buf = Buffer.new(capacity: 1024)
|
1205
|
+
TLS.push_client_hello(buf: tmp_buf, hello: hello)
|
1206
|
+
|
1207
|
+
# calculate binder
|
1208
|
+
hash_offset = tmp_buf.tell - binder_length - 3
|
1209
|
+
@key_schedule_psk.update_hash(tmp_buf.data_slice(start: 0, ends: hash_offset))
|
1210
|
+
binder = @key_schedule_psk.finished_verify_data(binder_key)
|
1211
|
+
hello.pre_shared_key.binders[0] = binder
|
1212
|
+
@key_schedule_psk.update_hash(tmp_buf.data_slice(start: hash_offset, ends: hash_offset + 3) + binder)
|
1213
|
+
|
1214
|
+
# calculate early data key
|
1215
|
+
if hello.early_data
|
1216
|
+
early_key = @key_schedule_psk.derive_secret("c e traffic")
|
1217
|
+
@update_traffic_key_cb.call(direction: Direction::ENCRYPT, epoch: Epoch::ZERO_RTT, cipher_suite: @key_schedule_psk.cipher_suite, secret: early_key)
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
@key_schedule_proxy = KeyScheduleProxy.new(@cipher_suites)
|
1222
|
+
@key_schedule_proxy.extract(nil)
|
1223
|
+
|
1224
|
+
TLS.push_message(key_schedule: @key_schedule_proxy, buf: output_buf) do
|
1225
|
+
TLS.push_client_hello(buf: output_buf, hello: hello)
|
1226
|
+
end
|
1227
|
+
set_state(State::CLIENT_EXPECT_SERVER_HELLO)
|
1228
|
+
end
|
1229
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
1230
|
+
|
1231
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Style/GuardClause
|
1232
|
+
def client_handle_hello(input_buf:, output_buf:) # rubocop:disable Lint/UnusedMethodArgument
|
1233
|
+
peer_hello = TLS.pull_server_hello(input_buf)
|
1234
|
+
|
1235
|
+
cipher_suite = TLS.negotiate(supported: @cipher_suites, offered: [peer_hello.cipher_suite], exc: AlertHandshakeFailure)
|
1236
|
+
|
1237
|
+
raise RuntimeError unless @legacy_compression_methods.include?(peer_hello.compression_method)
|
1238
|
+
raise RuntimeError unless @supported_versions.include?(peer_hello.supported_version)
|
1239
|
+
|
1240
|
+
# select key schedule
|
1241
|
+
if peer_hello.pre_shared_key
|
1242
|
+
raise AlertIllegalParameter if @key_schedule_psk.nil? || peer_hello.pre_shared_key != 0 || cipher_suite != @key_schedule_psk.cipher_suite
|
1243
|
+
|
1244
|
+
@key_schedule = @key_schedule_psk
|
1245
|
+
@session_resumed = true
|
1246
|
+
else
|
1247
|
+
@key_schedule = @key_schedule_proxy.select(cipher_suite)
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
@key_schedule_psk = nil
|
1251
|
+
@key_schedule_proxy = nil
|
1252
|
+
|
1253
|
+
# perform key exchange
|
1254
|
+
peer_public_key = TLS.decode_public_key(peer_hello.key_share)
|
1255
|
+
shared_key = nil
|
1256
|
+
|
1257
|
+
# X25519 nad X448 is not supported yet
|
1258
|
+
if peer_public_key.is_a?(OpenSSL::PKey::EC::Point) && @ec_key && peer_public_key.group.curve_name == @ec_key.group.curve_name
|
1259
|
+
shared_key = @ec_key.dh_compute_key(peer_public_key)
|
1260
|
+
else
|
1261
|
+
raise "Did not support yet"
|
1262
|
+
end
|
1263
|
+
raise RuntimeError unless shared_key
|
1264
|
+
|
1265
|
+
@key_schedule.update_hash(input_buf.data)
|
1266
|
+
@key_schedule.extract(shared_key)
|
1267
|
+
|
1268
|
+
setup_traffic_protection(Direction::DECRYPT, Epoch::HANDSHAKE, "s hs traffic")
|
1269
|
+
|
1270
|
+
set_state(State::CLIENT_EXPECT_ENCRYPTED_EXTENSIONS)
|
1271
|
+
end
|
1272
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Style/GuardClause
|
1273
|
+
|
1274
|
+
def client_handle_encrypted_extensions(input_buf)
|
1275
|
+
encrypted_extensions = TLS.pull_encrypted_extensions(input_buf)
|
1276
|
+
|
1277
|
+
@alpn_negotiated = encrypted_extensions.alpn_protocol
|
1278
|
+
@early_data_accepted = encrypted_extensions.early_data
|
1279
|
+
@received_extensions = encrypted_extensions.other_extensions
|
1280
|
+
@alpn_cb&.call(@alpn_negotiated)
|
1281
|
+
|
1282
|
+
setup_traffic_protection(Direction::ENCRYPT, Epoch::HANDSHAKE, "c hs traffic")
|
1283
|
+
@key_schedule.update_hash(input_buf.data)
|
1284
|
+
|
1285
|
+
# if the server accepted our PSK we are done, other we want its certificate
|
1286
|
+
if @session_resumed
|
1287
|
+
set_state(State::CLIENT_EXPECT_FINISHED)
|
1288
|
+
else
|
1289
|
+
set_state(State::CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE)
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
def client_handle_certificate(input_buf)
|
1294
|
+
certificate = TLS.pull_certificate(input_buf)
|
1295
|
+
|
1296
|
+
@peer_certificate = TLS.load_der_x509_certificate(certificate.certificates[0][0])
|
1297
|
+
@peer_certificate_chain = certificate.certificates.map.with_index do |cert, i|
|
1298
|
+
next if i == 0
|
1299
|
+
|
1300
|
+
TLS.load_der_x509_certificate(cert[0])
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
@key_schedule.update_hash(input_buf.data)
|
1304
|
+
set_state(State::CLIENT_EXPECT_CERTIFICATE_VERIFY)
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
def client_handle_certificate_verify(input_buf)
|
1308
|
+
verify = TLS.pull_certificate_verify(input_buf)
|
1309
|
+
raise RuntimeError unless @signature_algorithms.include?(verify.algorithm)
|
1310
|
+
|
1311
|
+
# check signature
|
1312
|
+
begin
|
1313
|
+
result = verify_with_params(
|
1314
|
+
cert: @peer_certificate,
|
1315
|
+
signature_algorithm: verify.algorithm,
|
1316
|
+
signature: verify.signature,
|
1317
|
+
verify_data: @key_schedule.certificate_verify_data("TLS 1.3, server CertificateVerify"),
|
1318
|
+
)
|
1319
|
+
raise AlertDecryptError unless result
|
1320
|
+
rescue OpenSSL::PKey::PKeyError
|
1321
|
+
raise AlertDecryptError
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
# check certificate
|
1325
|
+
if @verify_mode != OpenSSL::SSL::VERIFY_NONE
|
1326
|
+
TLS.verify_certificate(
|
1327
|
+
cadata: @cadata,
|
1328
|
+
cafile: @cafile,
|
1329
|
+
capath: @capath,
|
1330
|
+
certificate: @peer_certificate,
|
1331
|
+
chain: @peer_certificate_chain,
|
1332
|
+
server_name: @server_name,
|
1333
|
+
)
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
@key_schedule.update_hash(input_buf.data)
|
1337
|
+
set_state(State::CLIENT_EXPECT_FINISHED)
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
def client_handle_finished(input_buf:, output_buf:)
|
1341
|
+
finished = TLS.pull_finished(input_buf)
|
1342
|
+
|
1343
|
+
# check verify data
|
1344
|
+
expected_verify_data = @key_schedule.finished_verify_data(@dec_key)
|
1345
|
+
raise AlertDecryptError if finished.verify_data != expected_verify_data
|
1346
|
+
|
1347
|
+
@key_schedule.update_hash(input_buf.data)
|
1348
|
+
|
1349
|
+
# prepare traffic keys
|
1350
|
+
raise RuntimeError unless @key_schedule.generation == 2
|
1351
|
+
|
1352
|
+
@key_schedule.extract(nil)
|
1353
|
+
setup_traffic_protection(Direction::DECRYPT, Epoch::ONE_RTT, "s ap traffic")
|
1354
|
+
next_enc_key = @key_schedule.derive_secret("c ap traffic")
|
1355
|
+
|
1356
|
+
# send finished
|
1357
|
+
TLS.push_message(key_schedule: @key_schedule, buf: output_buf) do
|
1358
|
+
TLS.push_finished(buf: output_buf, finished: Finished.new.tap { |f| f.verify_data = @key_schedule.finished_verify_data(@enc_key) })
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
# commit traffic key
|
1362
|
+
@enc_key = next_enc_key
|
1363
|
+
@update_traffic_key_cb.call(direction: Direction::ENCRYPT, epoch: Epoch::ONE_RTT, cipher_suite: @key_schedule.cipher_suite, secret: @enc_key)
|
1364
|
+
set_state(State::CLIENT_POST_HANDSHAKE)
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
def client_handle_new_session_ticket(input_buf)
|
1368
|
+
new_session_ticket = TLS.pull_new_session_ticket(input_buf)
|
1369
|
+
|
1370
|
+
# notify application
|
1371
|
+
if @new_session_ticket_cb # rubocop:disable Style/GuardClause
|
1372
|
+
ticket = build_session_ticket(new_session_ticket: new_session_ticket, other_extensions: @received_extensions)
|
1373
|
+
@new_session_ticket_cb.call(ticket)
|
1374
|
+
end
|
1375
|
+
end
|
1376
|
+
|
1377
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
1378
|
+
def server_handle_hello(input_buf:, initial_buf:, handshake_buf:, onertt_buf:)
|
1379
|
+
peer_hello = TLS.pull_client_hello(input_buf)
|
1380
|
+
|
1381
|
+
# determine applicable signature algorithms
|
1382
|
+
signature_algorithms = if @certificate_private_key.is_a?(OpenSSL::PKey::RSA)
|
1383
|
+
[
|
1384
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA256,
|
1385
|
+
SignatureAlgorithm::RSA_PKCS1_SHA256,
|
1386
|
+
SignatureAlgorithm::RSA_PKCS1_SHA1,
|
1387
|
+
]
|
1388
|
+
elsif @certificate_private_key.is_a?(OpenSSL::PKey::EC) && @certificate_private_key.group.curve_name == "prime256v1"
|
1389
|
+
[SignatureAlgorithm::ECDSA_SECP256R1_SHA256]
|
1390
|
+
# elsif @certificate_private_key.is_a?(:ed25519) # TODO: https://github.com/ruby/openssl/pull/329
|
1391
|
+
# [SignatureAlgorithm::ED25519]
|
1392
|
+
# elsif @certificate_private_key.is_a?(:ed448)
|
1393
|
+
# [SignatureAlgorithm::ED448]
|
1394
|
+
else
|
1395
|
+
[]
|
1396
|
+
end
|
1397
|
+
# negotiate parameters
|
1398
|
+
cipher_suite = TLS.negotiate(supported: @cipher_suites, offered: peer_hello.cipher_suites, exc: AlertHandshakeFailure)
|
1399
|
+
compression_method =
|
1400
|
+
TLS.negotiate(supported: @legacy_compression_methods, offered: peer_hello.legacy_compression_methods, exc: AlertHandshakeFailure)
|
1401
|
+
psk_key_exchange_mode = TLS.negotiate(supported: @psk_key_exchange_modes, offered: peer_hello.psk_key_exchange_modes)
|
1402
|
+
signature_algorithm = TLS.negotiate(supported: signature_algorithms, offered: peer_hello.signature_algorithms, exc: AlertHandshakeFailure)
|
1403
|
+
supported_version = TLS.negotiate(supported: @supported_versions, offered: peer_hello.supported_versions, exc: AlertProtocolVersion)
|
1404
|
+
|
1405
|
+
# binding.irb
|
1406
|
+
# negotiate alpn
|
1407
|
+
if @alpn_protocols && !@alpn_protocols.empty?
|
1408
|
+
# binding.irb
|
1409
|
+
@alpn_negotiated = TLS.negotiate(supported: @alpn_protocols, offered: peer_hello.alpn_protocols, exc: AlertHandshakeFailure)
|
1410
|
+
end
|
1411
|
+
@alpn_cb&.call(@alpn_negotiated)
|
1412
|
+
|
1413
|
+
@client_random = peer_hello.random
|
1414
|
+
@server_random = Random.urandom(32)
|
1415
|
+
@legacy_session_id = peer_hello.legacy_session_id
|
1416
|
+
@received_extensions = peer_hello.other_extensions
|
1417
|
+
|
1418
|
+
# select key schedule
|
1419
|
+
pre_shared_key = nil
|
1420
|
+
if @get_session_ticket_cb &&
|
1421
|
+
psk_key_exchange_mode &&
|
1422
|
+
peer_hello.pre_shared_key &&
|
1423
|
+
peer_hello.pre_shared_key.identities.size == 1 &&
|
1424
|
+
peer_hello.pre_shared_key.binders.size == 1
|
1425
|
+
# ask application to find session ticket
|
1426
|
+
identity = peer_hello.pre_shared_key.identities[0]
|
1427
|
+
session_ticket = @get_session_ticket_cb.call(identity[0])
|
1428
|
+
|
1429
|
+
# validate session ticket
|
1430
|
+
if session_ticket&.is_valid && session_ticket&.cipher_suite == cipher_suite
|
1431
|
+
@key_schedule = KeySchedule.new(cipher_suite)
|
1432
|
+
@key_schedule.extract(session_ticket.resumption_secret)
|
1433
|
+
|
1434
|
+
binder_key = @key_schedule.derive_secret("res binder")
|
1435
|
+
binder_length = @key_schedule.hash.digest_length
|
1436
|
+
|
1437
|
+
hash_offset = input_buf.tell - binder_length - 3
|
1438
|
+
binder = input_buf.data_slice(start: hash_offset + 3, ends: hash_offset + 3 + binder_length)
|
1439
|
+
|
1440
|
+
@key_schedule.update_hash(input_buf.data_slice(start: 0, ends: hash_offset))
|
1441
|
+
expected_binder = @key_schedule.finished_verify_data(binder_key)
|
1442
|
+
|
1443
|
+
raise AlertHandshakeFailure if binder != expected_binder
|
1444
|
+
|
1445
|
+
@key_schedule.update_hash(input_buf.data_slice(start: hash_offset, ends: hash_offset + 3 + binder_length))
|
1446
|
+
@session_resumed = true
|
1447
|
+
|
1448
|
+
# calculate early data key
|
1449
|
+
if peer_hello.early_data
|
1450
|
+
early_key = @key_schedule.derive_secret("c e traffic")
|
1451
|
+
@early_data_accepted = true
|
1452
|
+
@update_traffic_key_cb.call(direction: Direction::DECRYPT, epoch: Epoch::ZERO_RTT, cipher_suite: @key_schedule.cipher_suite, secret: early_key)
|
1453
|
+
end
|
1454
|
+
pre_shared_key = 0
|
1455
|
+
end
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
# if PSK is not used, initialize key schedule
|
1459
|
+
if pre_shared_key.nil?
|
1460
|
+
@key_schedule = KeySchedule.new(cipher_suite)
|
1461
|
+
@key_schedule.extract(nil)
|
1462
|
+
@key_schedule.update_hash(input_buf.data)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
# perform key exchange
|
1466
|
+
public_key = nil
|
1467
|
+
shared_key = nil
|
1468
|
+
peer_hello.key_share.each do |key_share|
|
1469
|
+
peer_public_key = TLS.decode_public_key(key_share)
|
1470
|
+
case peer_public_key
|
1471
|
+
when OpenSSL::PKey::EC::Point
|
1472
|
+
@ec_key = OpenSSL::PKey::EC.generate(GROUP_TO_CURVE[key_share[0]])
|
1473
|
+
@ec_private_key = @ec_key.private_key
|
1474
|
+
public_key = @ec_key.public_key
|
1475
|
+
shared_key = @ec_key.dh_compute_key(peer_public_key)
|
1476
|
+
end
|
1477
|
+
end
|
1478
|
+
raise RuntimeError unless shared_key
|
1479
|
+
|
1480
|
+
# send hello
|
1481
|
+
hello = ServerHello.new.tap do |h|
|
1482
|
+
h.random = @server_random
|
1483
|
+
h.legacy_session_id = @legacy_session_id
|
1484
|
+
h.cipher_suite = cipher_suite
|
1485
|
+
h.compression_method = compression_method
|
1486
|
+
h.key_share = TLS.encode_public_key(public_key)
|
1487
|
+
h.pre_shared_key = pre_shared_key
|
1488
|
+
h.supported_version = supported_version
|
1489
|
+
h.other_extensions = []
|
1490
|
+
end
|
1491
|
+
TLS.push_message(key_schedule: @key_schedule, buf: initial_buf) do
|
1492
|
+
TLS.push_server_hello(buf: initial_buf, hello: hello)
|
1493
|
+
end
|
1494
|
+
@key_schedule.extract(shared_key)
|
1495
|
+
|
1496
|
+
setup_traffic_protection(Direction::ENCRYPT, Epoch::HANDSHAKE, "s hs traffic")
|
1497
|
+
setup_traffic_protection(Direction::DECRYPT, Epoch::HANDSHAKE, "c hs traffic")
|
1498
|
+
|
1499
|
+
# send encrypted extensions
|
1500
|
+
TLS.push_message(key_schedule: @key_schedule, buf: handshake_buf) do
|
1501
|
+
ext = EncryptedExtensions.new.tap do |e|
|
1502
|
+
e.alpn_protocol = @alpn_negotiated
|
1503
|
+
e.early_data = @early_data_accepted
|
1504
|
+
e.other_extensions = @handshake_extensions
|
1505
|
+
end
|
1506
|
+
TLS.push_encrypted_extensions(buf: handshake_buf, extensions: ext)
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
unless pre_shared_key
|
1510
|
+
# send certificate
|
1511
|
+
TLS.push_message(key_schedule: @key_schedule, buf: handshake_buf) do
|
1512
|
+
cert = Certificate.new.tap do |c|
|
1513
|
+
c.request_context = ""
|
1514
|
+
c.certificates = ([@certificate] + (@certificate_chain || [])).map { |x| [x.to_der, ""] }
|
1515
|
+
end
|
1516
|
+
TLS.push_certificate(buf: handshake_buf, certificate: cert)
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
# send certificate verify
|
1520
|
+
signature = sign_with_params(
|
1521
|
+
priv_key: @certificate_private_key,
|
1522
|
+
signature_algorithm: signature_algorithm,
|
1523
|
+
verify_data: @key_schedule.certificate_verify_data("TLS 1.3, server CertificateVerify"),
|
1524
|
+
)
|
1525
|
+
|
1526
|
+
TLS.push_message(key_schedule: @key_schedule, buf: handshake_buf) do
|
1527
|
+
verify = CertificateVerify.new.tap do |cv|
|
1528
|
+
cv.algorithm = signature_algorithm
|
1529
|
+
cv.signature = signature
|
1530
|
+
end
|
1531
|
+
TLS.push_certificate_verify(buf: handshake_buf, verify: verify)
|
1532
|
+
end
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
# send finished
|
1536
|
+
TLS.push_message(key_schedule: @key_schedule, buf: handshake_buf) do
|
1537
|
+
finished = Finished.new.tap do |f|
|
1538
|
+
f.verify_data = @key_schedule.finished_verify_data(@enc_key)
|
1539
|
+
end
|
1540
|
+
TLS.push_finished(buf: handshake_buf, finished: finished)
|
1541
|
+
end
|
1542
|
+
|
1543
|
+
# prepare traffic keys
|
1544
|
+
raise RuntimeError unless @key_schedule.generation == 2
|
1545
|
+
|
1546
|
+
@key_schedule.extract(nil)
|
1547
|
+
setup_traffic_protection(Direction::ENCRYPT, Epoch::ONE_RTT, "s ap traffic")
|
1548
|
+
@next_dec_key = @key_schedule.derive_secret("c ap traffic")
|
1549
|
+
|
1550
|
+
# anticipate client's FINISHED as we don't use client auth
|
1551
|
+
@expected_verify_data = @key_schedule.finished_verify_data(@dec_key)
|
1552
|
+
buf = Buffer.new(capacity: 64)
|
1553
|
+
TLS.push_finished(buf: buf, finished: Finished.new.tap { |f| f.verify_data = @expected_verify_data })
|
1554
|
+
@key_schedule.update_hash(buf.data)
|
1555
|
+
|
1556
|
+
# create a new session ticket
|
1557
|
+
if @new_session_ticket_cb && psk_key_exchange_mode
|
1558
|
+
@new_session_ticket = NewSessionTicket.new.tap do |st|
|
1559
|
+
st.ticket_lifetime = 86400
|
1560
|
+
st.ticket_age_add = Random.urandom(4).unpack1("I")
|
1561
|
+
st.ticket_nonce = ""
|
1562
|
+
st.ticket = Random.urandom(64)
|
1563
|
+
st.max_early_data_size = @max_early_data
|
1564
|
+
st.other_extensions = []
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
# send message
|
1568
|
+
TLS.push_new_session_ticket(buf: onertt_buf, new_session_ticket: @new_session_ticket)
|
1569
|
+
|
1570
|
+
# notify application
|
1571
|
+
ticket = build_session_ticket(new_session_ticket: @new_session_ticket, other_extensions: @handshake_extensions)
|
1572
|
+
@new_session_ticket_cb.call(ticket)
|
1573
|
+
end
|
1574
|
+
|
1575
|
+
set_state(State::SERVER_EXPECT_FINISHED)
|
1576
|
+
end
|
1577
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
1578
|
+
|
1579
|
+
def server_handle_finished(input_buf:, output_buf:) # rubocop:disable Lint/UnusedMethodArgument
|
1580
|
+
finished = TLS.pull_finished(input_buf)
|
1581
|
+
|
1582
|
+
# ckeck verify data
|
1583
|
+
raise AlertDecryptError if finished.verify_data != @expected_verify_data
|
1584
|
+
|
1585
|
+
# commit traffic key
|
1586
|
+
@dec_key = @next_dec_key
|
1587
|
+
@next_dec_key = nil
|
1588
|
+
@update_traffic_key_cb.call(direction: Direction::DECRYPT, epoch: Epoch::ONE_RTT, cipher_suite: @key_schedule.cipher_suite, secret: @dec_key)
|
1589
|
+
set_state(State::SERVER_POST_HANDSHAKE)
|
1590
|
+
end
|
1591
|
+
|
1592
|
+
def setup_traffic_protection(direction, epoch, label)
|
1593
|
+
key = @key_schedule.derive_secret(label)
|
1594
|
+
|
1595
|
+
if direction == Direction::ENCRYPT
|
1596
|
+
@enc_key = key
|
1597
|
+
else
|
1598
|
+
@dec_key = key
|
1599
|
+
end
|
1600
|
+
@update_traffic_key_cb.call(direction: direction, epoch: epoch, cipher_suite: @key_schedule.cipher_suite, secret: key)
|
1601
|
+
end
|
1602
|
+
|
1603
|
+
def set_state(state)
|
1604
|
+
# TODO: logger
|
1605
|
+
@state = state
|
1606
|
+
end
|
1607
|
+
|
1608
|
+
def sign_with_params(priv_key:, signature_algorithm:, verify_data:)
|
1609
|
+
case signature_algorithm
|
1610
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA256,
|
1611
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA256,
|
1612
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA256
|
1613
|
+
priv_key.sign_pss("SHA256", verify_data, salt_length: :digest, mgf1_hash: "SHA256")
|
1614
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA384,
|
1615
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA384,
|
1616
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA384
|
1617
|
+
priv_key.sign_pss("SHA384", verify_data, salt_length: :digest, mgf1_hash: "SHA384")
|
1618
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA512,
|
1619
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA512,
|
1620
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA512
|
1621
|
+
priv_key.sign_pss("SHA512", verify_data, salt_length: :digest, mgf1_hash: "SHA512")
|
1622
|
+
when SignatureAlgorithm::ECDSA_SECP256R1_SHA256
|
1623
|
+
priv_key.sign("SHA256", verify_data)
|
1624
|
+
when SignatureAlgorithm::ECDSA_SECP384R1_SHA384
|
1625
|
+
priv_key.sign("SHA384", verify_data)
|
1626
|
+
when SignatureAlgorithm::ECDSA_SECP521R1_SHA512
|
1627
|
+
priv_key.sign("SHA512", verify_data)
|
1628
|
+
else
|
1629
|
+
raise RuntimeError
|
1630
|
+
end
|
1631
|
+
end
|
1632
|
+
|
1633
|
+
def verify_with_params(cert:, signature_algorithm:, signature:, verify_data:)
|
1634
|
+
case signature_algorithm
|
1635
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA256,
|
1636
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA256,
|
1637
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA256
|
1638
|
+
cert.public_key.verify_pss("SHA256", signature, verify_data, salt_length: :auto, mgf1_hash: "SHA256")
|
1639
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA384,
|
1640
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA384,
|
1641
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA384
|
1642
|
+
cert.public_key.verify_pss("SHA384", signature, verify_data, salt_length: :auto, mgf1_hash: "SHA384")
|
1643
|
+
when SignatureAlgorithm::RSA_PKCS1_SHA512,
|
1644
|
+
SignatureAlgorithm::RSA_PSS_RSAE_SHA512,
|
1645
|
+
SignatureAlgorithm::RSA_PSS_PSS_SHA512
|
1646
|
+
cert.public_key.verify_pss("SHA512", signature, verify_data, salt_length: :auto, mgf1_hash: "SHA512")
|
1647
|
+
when SignatureAlgorithm::ECDSA_SECP256R1_SHA256
|
1648
|
+
cert.public_key.verify("SHA256", signature, verify_data)
|
1649
|
+
when SignatureAlgorithm::ECDSA_SECP384R1_SHA384
|
1650
|
+
cert.public_key.verify("SHA384", signature, verify_data)
|
1651
|
+
when SignatureAlgorithm::ECDSA_SECP521R1_SHA512
|
1652
|
+
cert.public_key.verify("SHA512", signature, verify_data)
|
1653
|
+
else
|
1654
|
+
raise RuntimeError
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
end
|
1659
|
+
end
|