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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 50df67e1d621da0a19746a7461c07da05856348bdc6eed93048b25e18464d37f
|
4
|
+
data.tar.gz: c9c1b0ae4663816ae8f01695a413674bed8983b059f97717671d41b42e5f1def
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2e8e5ac87b46489f8e10a9f95a46eb98815bb63aa0d03068cdea0b0f883d9cf77e6b40ffdcc9ad592191b6b6c1ca3788ddfafb21bbef70cd45d547fb50b94bc4
|
7
|
+
data.tar.gz: 553144700d2e68044e8bb6160e100bea9a1e44f46b970cc0cd1200cf0861ea8a05d29d3622e1d73e7928f92a310a9a40bbea057a3a03f3463558302abeff7d34
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Tomoya Kuwayama
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# tttls1.3
|
2
|
+
|
3
|
+
[](https://travis-ci.org/thekuwayama/tttls1.3) [](https://codeclimate.com/github/thekuwayama/tttls1.3/maintainability)
|
4
|
+
|
5
|
+
tttls1.3 is Ruby implementation of [TLS 1.3](https://tools.ietf.org/html/rfc8446) protocol.
|
6
|
+
tttls1.3 uses [openssl](https://github.com/ruby/openssl) as backend for crypto and X.509 operations.
|
7
|
+
|
8
|
+
It is the purpose of this project to understand the TLS 1.3 protocol and implement the TLS 1.3 protocol using Ruby.
|
9
|
+
Backward compatibility and performance are not an objective.
|
10
|
+
This gem should not be used for production software.
|
11
|
+
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
tttls1.3 provides client API with the following features:
|
16
|
+
|
17
|
+
* Simple 1-RTT Handshake
|
18
|
+
* HelloRetryRequest
|
19
|
+
* Resumed 0-RTT Handshake (with PSK from ticket)
|
20
|
+
|
21
|
+
NOT supports X25519, X448, FFDHE, AES-CCM, Client Authentication, Post-Handshake Authentication, KeyUpdate, external PSKs.
|
22
|
+
|
23
|
+
|
24
|
+
## Getting started
|
25
|
+
|
26
|
+
tttls1.3 gem is available at [rubygems.org](https://rubygems.org/gems/tttls1.3). You can install with:
|
27
|
+
|
28
|
+
```
|
29
|
+
$ gem install tttls1.3
|
30
|
+
```
|
31
|
+
|
32
|
+
This implementation provides only minimal API, so your code is responsible for application layer.
|
33
|
+
Roughly, this works as follows:
|
34
|
+
|
35
|
+
```
|
36
|
+
require 'tttls1.3'
|
37
|
+
|
38
|
+
socket = YourTransport.new
|
39
|
+
client = TTTLS13::Client.new(socket, YOUR_HOSTNAME)
|
40
|
+
client.connect
|
41
|
+
|
42
|
+
client.write(YOUR_MESSAGE)
|
43
|
+
client.read
|
44
|
+
client.close
|
45
|
+
```
|
46
|
+
|
47
|
+
HTTPS examples are [here](https://github.com/thekuwayama/tttls1.3/tree/master/example).
|
48
|
+
|
49
|
+
|
50
|
+
## License
|
51
|
+
|
52
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'openssl'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
RuboCop::RakeTask.new
|
10
|
+
|
11
|
+
TMP_DIR = __dir__ + '/tmp'
|
12
|
+
CA_KEY = TMP_DIR + '/ca.key'
|
13
|
+
CA_CRT = TMP_DIR + '/ca.crt'
|
14
|
+
SERVER_KEY = TMP_DIR + '/server.key'
|
15
|
+
SERVER_CRT = TMP_DIR + '/server.crt'
|
16
|
+
certs = [CA_KEY, CA_CRT, SERVER_KEY, SERVER_CRT]
|
17
|
+
|
18
|
+
directory TMP_DIR
|
19
|
+
|
20
|
+
file CA_KEY => TMP_DIR do
|
21
|
+
puts "generate #{CA_KEY}..."
|
22
|
+
ca_key = OpenSSL::PKey::RSA.generate(4096)
|
23
|
+
File.write(CA_KEY, ca_key.to_pem)
|
24
|
+
end
|
25
|
+
|
26
|
+
file CA_CRT => [TMP_DIR, CA_KEY] do
|
27
|
+
ca_key = OpenSSL::PKey::RSA.new(File.read(CA_KEY))
|
28
|
+
|
29
|
+
puts "generate #{CA_CRT}..."
|
30
|
+
issu = sub = OpenSSL::X509::Name.new
|
31
|
+
sub.add_entry('CN', 'test-ca')
|
32
|
+
|
33
|
+
ca_crt = OpenSSL::X509::Certificate.new
|
34
|
+
ca_crt.not_before = Time.now
|
35
|
+
ca_crt.not_after = Time.now + (60 * 60 * 24 * 365 * 10)
|
36
|
+
ca_crt.public_key = ca_key.public_key
|
37
|
+
ca_crt.serial = 1
|
38
|
+
ca_crt.version = 2
|
39
|
+
ca_crt.issuer = issu
|
40
|
+
ca_crt.subject = sub
|
41
|
+
|
42
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
43
|
+
factory.subject_certificate = ca_crt
|
44
|
+
factory.issuer_certificate = ca_crt
|
45
|
+
ca_crt.add_extension(
|
46
|
+
factory.create_extension(
|
47
|
+
'keyUsage',
|
48
|
+
'critical, cRLSign, keyCertSign'
|
49
|
+
)
|
50
|
+
)
|
51
|
+
ca_crt.add_extension(
|
52
|
+
factory.create_extension(
|
53
|
+
'basicConstraints',
|
54
|
+
'critical, CA:true'
|
55
|
+
)
|
56
|
+
)
|
57
|
+
ca_crt.add_extension(
|
58
|
+
factory.create_extension(
|
59
|
+
'subjectKeyIdentifier',
|
60
|
+
'hash'
|
61
|
+
)
|
62
|
+
)
|
63
|
+
|
64
|
+
digest = OpenSSL::Digest::SHA256.new
|
65
|
+
ca_crt.sign(ca_key, digest)
|
66
|
+
File.write(CA_CRT, ca_crt.to_pem)
|
67
|
+
end
|
68
|
+
|
69
|
+
file SERVER_KEY => TMP_DIR do
|
70
|
+
puts "generate #{SERVER_KEY}..."
|
71
|
+
server_key = OpenSSL::PKey::RSA.generate(2048)
|
72
|
+
File.write(SERVER_KEY, server_key.to_pem)
|
73
|
+
end
|
74
|
+
|
75
|
+
file SERVER_CRT => [TMP_DIR, CA_CRT, SERVER_KEY] do
|
76
|
+
ca_key = OpenSSL::PKey::RSA.new(File.read(CA_KEY))
|
77
|
+
ca_crt = OpenSSL::X509::Certificate.new(File.read(CA_CRT))
|
78
|
+
server_key = OpenSSL::PKey::RSA.new(File.read(SERVER_KEY))
|
79
|
+
|
80
|
+
puts "generate #{SERVER_CRT}..."
|
81
|
+
sub = OpenSSL::X509::Name.new
|
82
|
+
sub.add_entry('CN', 'localhost')
|
83
|
+
|
84
|
+
server_crt = OpenSSL::X509::Certificate.new
|
85
|
+
server_crt.not_before = Time.now
|
86
|
+
server_crt.not_after = Time.now + (60 * 60 * 24 * 365)
|
87
|
+
server_crt.public_key = server_key.public_key
|
88
|
+
server_crt.serial = 2
|
89
|
+
server_crt.version = 2
|
90
|
+
server_crt.issuer = ca_crt.issuer
|
91
|
+
server_crt.subject = sub
|
92
|
+
|
93
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
94
|
+
factory.subject_certificate = server_crt
|
95
|
+
factory.issuer_certificate = ca_crt
|
96
|
+
server_crt.add_extension(
|
97
|
+
factory.create_extension(
|
98
|
+
'basicConstraints',
|
99
|
+
'CA:FALSE'
|
100
|
+
)
|
101
|
+
)
|
102
|
+
server_crt.add_extension(
|
103
|
+
factory.create_extension(
|
104
|
+
'keyUsage',
|
105
|
+
'digitalSignature, keyEncipherment'
|
106
|
+
)
|
107
|
+
)
|
108
|
+
server_crt.add_extension(
|
109
|
+
factory.create_extension(
|
110
|
+
'subjectAltName',
|
111
|
+
'DNS:localhost'
|
112
|
+
)
|
113
|
+
)
|
114
|
+
|
115
|
+
digest = OpenSSL::Digest::SHA256.new
|
116
|
+
server_crt.sign(ca_key, digest)
|
117
|
+
File.write(SERVER_CRT, server_crt.to_pem)
|
118
|
+
end
|
119
|
+
|
120
|
+
desc 'generate ' + certs.map { |path| File.basename(path) }.join(', ')
|
121
|
+
task gen_certs: certs
|
122
|
+
|
123
|
+
desc 'delete ' + certs.map { |path| File.basename(path) }.join(', ')
|
124
|
+
task :del_certs do
|
125
|
+
certs.each do |path|
|
126
|
+
puts "delete #{path}..."
|
127
|
+
FileUtils.rm(path, force: true)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
RSpec::Core::RakeTask.new(spec: :gen_certs)
|
132
|
+
|
133
|
+
task default: %i[rubocop spec]
|
data/example/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH << __dir__ + '/../lib'
|
4
|
+
|
5
|
+
require 'socket'
|
6
|
+
require 'openssl'
|
7
|
+
require 'tttls1.3'
|
8
|
+
|
9
|
+
def http_get(hostname)
|
10
|
+
<<~BIN
|
11
|
+
GET / HTTP/1.1\r
|
12
|
+
Host: #{hostname}\r
|
13
|
+
User-Agent: https_client\r
|
14
|
+
Accept: */*\r
|
15
|
+
\r
|
16
|
+
BIN
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'helper'
|
5
|
+
|
6
|
+
hostname, port = (ARGV[0] || 'localhost:4433').split(':')
|
7
|
+
http_get = http_get(hostname)
|
8
|
+
|
9
|
+
socket = TCPSocket.new(hostname, port)
|
10
|
+
settings = { ca_file: __dir__ + '/../tmp/ca.crt' }
|
11
|
+
client = TTTLS13::Client.new(socket, hostname, settings)
|
12
|
+
client.connect
|
13
|
+
client.write(http_get)
|
14
|
+
|
15
|
+
# status line, header
|
16
|
+
buffer = ''
|
17
|
+
buffer += client.read until buffer.include?("\r\n\r\n")
|
18
|
+
print header = buffer.split("\r\n\r\n").first
|
19
|
+
# header; Content-Length
|
20
|
+
cl_line = header.split("\r\n").find { |s| s.match(/Content-Length:/i) }
|
21
|
+
|
22
|
+
# body
|
23
|
+
unless cl_line.nil?
|
24
|
+
cl = cl_line.split(':').last.to_i
|
25
|
+
print buffer = buffer.split("\r\n\r\n")[1..].join
|
26
|
+
while buffer.length < cl
|
27
|
+
print s = client.read
|
28
|
+
buffer += s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
client.close
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'helper'
|
5
|
+
|
6
|
+
hostname, port = (ARGV[0] || 'localhost:4433').split(':')
|
7
|
+
http_get = http_get(hostname)
|
8
|
+
|
9
|
+
settings_2nd = {
|
10
|
+
ca_file: __dir__ + '/../tmp/ca.crt'
|
11
|
+
}
|
12
|
+
process_new_session_ticket = proc do |nst, rms, cs|
|
13
|
+
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
14
|
+
|
15
|
+
settings_2nd[:ticket] = nst.ticket
|
16
|
+
settings_2nd[:resumption_master_secret] = rms
|
17
|
+
settings_2nd[:psk_cipher_suite] = cs
|
18
|
+
settings_2nd[:ticket_nonce] = nst.ticket_nonce
|
19
|
+
settings_2nd[:ticket_age_add] = nst.ticket_age_add
|
20
|
+
settings_2nd[:ticket_timestamp] = nst.timestamp
|
21
|
+
end
|
22
|
+
settings_1st = {
|
23
|
+
ca_file: __dir__ + '/../tmp/ca.crt',
|
24
|
+
process_new_session_ticket: process_new_session_ticket
|
25
|
+
}
|
26
|
+
accepted_early_data = false
|
27
|
+
[
|
28
|
+
# Initial Handshake:
|
29
|
+
settings_1st,
|
30
|
+
# Subsequent Handshake:
|
31
|
+
settings_2nd
|
32
|
+
].each_with_index do |settings, i|
|
33
|
+
socket = TCPSocket.new(hostname, port)
|
34
|
+
client = TTTLS13::Client.new(socket, hostname, settings)
|
35
|
+
|
36
|
+
# send message using early data; 0-RTT
|
37
|
+
client.early_data(http_get) if i == 1 && settings.include?(:ticket)
|
38
|
+
client.connect
|
39
|
+
# send message after Simple 1-RTT Handshake
|
40
|
+
client.write(http_get) if i.zero? || !client.accepted_early_data?
|
41
|
+
|
42
|
+
# status line, header
|
43
|
+
buffer = ''
|
44
|
+
buffer += client.read until buffer.include?("\r\n\r\n")
|
45
|
+
print header = buffer.split("\r\n\r\n").first
|
46
|
+
# header; Content-Length
|
47
|
+
cl_line = header.split("\r\n").find { |s| s.match(/Content-Length:/i) }
|
48
|
+
|
49
|
+
# body
|
50
|
+
unless cl_line.nil?
|
51
|
+
cl = cl_line.split(':').last.to_i
|
52
|
+
print buffer = buffer.split("\r\n\r\n")[1..].join
|
53
|
+
while buffer.length < cl
|
54
|
+
print s = client.read
|
55
|
+
buffer += s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
client.close
|
60
|
+
accepted_early_data = client.accepted_early_data?
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "\n" + '-' * 10
|
64
|
+
puts "early data of 2nd handshake: #{accepted_early_data}"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'helper'
|
5
|
+
|
6
|
+
hostname, port = (ARGV[0] || 'localhost:4433').split(':')
|
7
|
+
http_get = http_get(hostname)
|
8
|
+
|
9
|
+
socket = TCPSocket.new(hostname, port)
|
10
|
+
settings = {
|
11
|
+
ca_file: __dir__ + '/../tmp/ca.crt',
|
12
|
+
key_share_groups: [] # empty KeyShareClientHello.client_shares
|
13
|
+
}
|
14
|
+
client = TTTLS13::Client.new(socket, hostname, settings)
|
15
|
+
client.connect
|
16
|
+
client.write(http_get)
|
17
|
+
|
18
|
+
# status line, header
|
19
|
+
buffer = ''
|
20
|
+
buffer += client.read until buffer.include?("\r\n\r\n")
|
21
|
+
print header = buffer.split("\r\n\r\n").first
|
22
|
+
# header; Content-Length
|
23
|
+
cl_line = header.split("\r\n").find { |s| s.match(/Content-Length:/i) }
|
24
|
+
|
25
|
+
# body
|
26
|
+
unless cl_line.nil?
|
27
|
+
cl = cl_line.split(':').last.to_i
|
28
|
+
print buffer = buffer.split("\r\n\r\n")[1..].join
|
29
|
+
while buffer.length < cl
|
30
|
+
print s = client.read
|
31
|
+
buffer += s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
client.close
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'helper'
|
5
|
+
|
6
|
+
hostname, port = (ARGV[0] || 'localhost:4433').split(':')
|
7
|
+
http_get = http_get(hostname)
|
8
|
+
|
9
|
+
settings_2nd = {
|
10
|
+
ca_file: __dir__ + '/../tmp/ca.crt'
|
11
|
+
}
|
12
|
+
process_new_session_ticket = proc do |nst, rms, cs|
|
13
|
+
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
14
|
+
|
15
|
+
settings_2nd[:ticket] = nst.ticket
|
16
|
+
settings_2nd[:resumption_master_secret] = rms
|
17
|
+
settings_2nd[:psk_cipher_suite] = cs
|
18
|
+
settings_2nd[:ticket_nonce] = nst.ticket_nonce
|
19
|
+
settings_2nd[:ticket_age_add] = nst.ticket_age_add
|
20
|
+
settings_2nd[:ticket_timestamp] = nst.timestamp
|
21
|
+
end
|
22
|
+
settings_1st = {
|
23
|
+
ca_file: __dir__ + '/../tmp/ca.crt',
|
24
|
+
process_new_session_ticket: process_new_session_ticket
|
25
|
+
}
|
26
|
+
|
27
|
+
[
|
28
|
+
# Initial Handshake:
|
29
|
+
settings_1st,
|
30
|
+
# Subsequent Handshake:
|
31
|
+
settings_2nd
|
32
|
+
].each do |settings|
|
33
|
+
socket = TCPSocket.new(hostname, port)
|
34
|
+
client = TTTLS13::Client.new(socket, hostname, settings)
|
35
|
+
client.connect
|
36
|
+
client.write(http_get)
|
37
|
+
|
38
|
+
# status line, header
|
39
|
+
buffer = ''
|
40
|
+
buffer += client.read until buffer.include?("\r\n\r\n")
|
41
|
+
print header = buffer.split("\r\n\r\n").first
|
42
|
+
# header; Content-Length
|
43
|
+
cl_line = header.split("\r\n").find { |s| s.match(/Content-Length:/i) }
|
44
|
+
|
45
|
+
# body
|
46
|
+
unless cl_line.nil?
|
47
|
+
cl = cl_line.split(':').last.to_i
|
48
|
+
print buffer = buffer.split("\r\n\r\n")[1..].join
|
49
|
+
while buffer.length < cl
|
50
|
+
print s = client.read
|
51
|
+
buffer += s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
client.close
|
56
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module CipherSuite
|
7
|
+
TLS_AES_128_GCM_SHA256 = "\x13\x01"
|
8
|
+
TLS_AES_256_GCM_SHA384 = "\x13\x02"
|
9
|
+
TLS_CHACHA20_POLY1305_SHA256 = "\x13\x03"
|
10
|
+
# TLS_AES_128_CCM_SHA256 = "\x13\x04" # UNSUPPORTED
|
11
|
+
# TLS_AES_128_CCM_8_SHA256 = "\x13\x05" # UNSUPPORTED
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def digest(cipher_suite)
|
15
|
+
case cipher_suite
|
16
|
+
when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
|
17
|
+
# , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
|
18
|
+
'SHA256'
|
19
|
+
when TLS_AES_256_GCM_SHA384
|
20
|
+
'SHA384'
|
21
|
+
else
|
22
|
+
raise Error::ErrorAlerts, :internal_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash_len(cipher_suite)
|
27
|
+
case cipher_suite
|
28
|
+
when TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
|
29
|
+
# , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
|
30
|
+
32
|
31
|
+
when TLS_AES_256_GCM_SHA384
|
32
|
+
48
|
33
|
+
else
|
34
|
+
raise Error::ErrorAlerts, :internal_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def key_len(cipher_suite)
|
39
|
+
case cipher_suite
|
40
|
+
when TLS_AES_128_GCM_SHA256
|
41
|
+
# , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
|
42
|
+
16
|
43
|
+
when TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
|
44
|
+
32
|
45
|
+
else
|
46
|
+
raise Error::ErrorAlerts, :internal_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def iv_len(cipher_suite)
|
51
|
+
case cipher_suite
|
52
|
+
when TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
|
53
|
+
TLS_CHACHA20_POLY1305_SHA256
|
54
|
+
# , TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256
|
55
|
+
12
|
56
|
+
else
|
57
|
+
raise Error::ErrorAlerts, :internal_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class CipherSuites < Array
|
64
|
+
# @param cipher_suites [Array of CipherSuite]
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# CipherSuites.new([
|
68
|
+
# CipherSuite::TLS_AES_256_GCM_SHA384,
|
69
|
+
# CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
|
70
|
+
# CipherSuite::TLS_AES_128_GCM_SHA256
|
71
|
+
# ])
|
72
|
+
def initialize(cipher_suites)
|
73
|
+
super(cipher_suites)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [String]
|
77
|
+
def serialize
|
78
|
+
join.prefix_uint16_length
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param binary [String]
|
82
|
+
#
|
83
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
84
|
+
#
|
85
|
+
# @return [TTTLS13::CipherSuites]
|
86
|
+
def self.deserialize(binary)
|
87
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
88
|
+
|
89
|
+
cipher_suites = []
|
90
|
+
i = 0
|
91
|
+
while i < binary.length
|
92
|
+
raise Error::ErrorAlerts, :decode_error if i + 2 > binary.length
|
93
|
+
|
94
|
+
cipher_suites << binary.slice(i, 2)
|
95
|
+
i += 2
|
96
|
+
end
|
97
|
+
raise Error::ErrorAlerts, :decode_error unless i == binary.length
|
98
|
+
|
99
|
+
CipherSuites.new(cipher_suites)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|