tttls1.3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +8 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +133 -0
- data/example/helper.rb +17 -0
- data/example/https_client.rb +32 -0
- data/example/https_client_using_0rtt.rb +64 -0
- data/example/https_client_using_hrr.rb +35 -0
- data/example/https_client_using_ticket.rb +56 -0
- data/lib/tttls1.3/cipher_suites.rb +102 -0
- data/lib/tttls1.3/client.rb +745 -0
- data/lib/tttls1.3/connection.rb +380 -0
- data/lib/tttls1.3/cryptograph/aead.rb +118 -0
- data/lib/tttls1.3/cryptograph/passer.rb +22 -0
- data/lib/tttls1.3/cryptograph.rb +3 -0
- data/lib/tttls1.3/error.rb +22 -0
- data/lib/tttls1.3/key_schedule.rb +242 -0
- data/lib/tttls1.3/message/alert.rb +86 -0
- data/lib/tttls1.3/message/application_data.rb +27 -0
- data/lib/tttls1.3/message/certificate.rb +121 -0
- data/lib/tttls1.3/message/certificate_verify.rb +59 -0
- data/lib/tttls1.3/message/change_cipher_spec.rb +26 -0
- data/lib/tttls1.3/message/client_hello.rb +100 -0
- data/lib/tttls1.3/message/encrypted_extensions.rb +65 -0
- data/lib/tttls1.3/message/end_of_early_data.rb +29 -0
- data/lib/tttls1.3/message/extension/alpn.rb +70 -0
- data/lib/tttls1.3/message/extension/cookie.rb +47 -0
- data/lib/tttls1.3/message/extension/early_data_indication.rb +58 -0
- data/lib/tttls1.3/message/extension/key_share.rb +236 -0
- data/lib/tttls1.3/message/extension/pre_shared_key.rb +205 -0
- data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +54 -0
- data/lib/tttls1.3/message/extension/record_size_limit.rb +46 -0
- data/lib/tttls1.3/message/extension/server_name.rb +91 -0
- data/lib/tttls1.3/message/extension/signature_algorithms.rb +69 -0
- data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +25 -0
- data/lib/tttls1.3/message/extension/status_request.rb +106 -0
- data/lib/tttls1.3/message/extension/supported_groups.rb +145 -0
- data/lib/tttls1.3/message/extension/supported_versions.rb +98 -0
- data/lib/tttls1.3/message/extension/unknown_extension.rb +38 -0
- data/lib/tttls1.3/message/extensions.rb +173 -0
- data/lib/tttls1.3/message/finished.rb +44 -0
- data/lib/tttls1.3/message/new_session_ticket.rb +89 -0
- data/lib/tttls1.3/message/record.rb +232 -0
- data/lib/tttls1.3/message/server_hello.rb +116 -0
- data/lib/tttls1.3/message.rb +48 -0
- data/lib/tttls1.3/sequence_number.rb +31 -0
- data/lib/tttls1.3/signature_scheme.rb +31 -0
- data/lib/tttls1.3/transcript.rb +69 -0
- data/lib/tttls1.3/utils.rb +91 -0
- data/lib/tttls1.3/version.rb +5 -0
- data/lib/tttls1.3.rb +16 -0
- data/spec/aead_spec.rb +95 -0
- data/spec/alert_spec.rb +54 -0
- data/spec/alpn_spec.rb +55 -0
- data/spec/application_data_spec.rb +26 -0
- data/spec/certificate_spec.rb +55 -0
- data/spec/certificate_verify_spec.rb +51 -0
- data/spec/change_cipher_spec_spec.rb +26 -0
- data/spec/cipher_suites_spec.rb +39 -0
- data/spec/client_hello_spec.rb +83 -0
- data/spec/client_spec.rb +319 -0
- data/spec/connection_spec.rb +114 -0
- data/spec/cookie_spec.rb +98 -0
- data/spec/early_data_indication_spec.rb +64 -0
- data/spec/encrypted_extensions_spec.rb +94 -0
- data/spec/error_spec.rb +18 -0
- data/spec/extensions_spec.rb +170 -0
- data/spec/finished_spec.rb +55 -0
- data/spec/key_schedule_spec.rb +198 -0
- data/spec/key_share_spec.rb +199 -0
- data/spec/new_session_ticket_spec.rb +80 -0
- data/spec/pre_shared_key_spec.rb +167 -0
- data/spec/psk_key_exchange_modes_spec.rb +45 -0
- data/spec/record_size_limit_spec.rb +61 -0
- data/spec/record_spec.rb +105 -0
- data/spec/server_hello_spec.rb +101 -0
- data/spec/server_name_spec.rb +110 -0
- data/spec/signature_algorithms_cert_spec.rb +73 -0
- data/spec/signature_algorithms_spec.rb +100 -0
- data/spec/spec_helper.rb +872 -0
- data/spec/status_request_spec.rb +73 -0
- data/spec/supported_groups_spec.rb +79 -0
- data/spec/supported_versions_spec.rb +136 -0
- data/spec/transcript_spec.rb +69 -0
- data/spec/unknown_extension_spec.rb +90 -0
- data/spec/utils_spec.rb +215 -0
- data/tttls1.3.gemspec +25 -0
- metadata +197 -0
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
|
+
[![Build Status](https://travis-ci.org/thekuwayama/tttls1.3.svg?branch=master)](https://travis-ci.org/thekuwayama/tttls1.3) [![Maintainability](https://api.codeclimate.com/v1/badges/47f3c267d9cfd2c8e388/maintainability)](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
|